From: Thomas Lamprecht Date: Thu, 25 May 2023 16:10:14 +0000 (+0200) Subject: separate packaging and source build system X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=6029cbb071c3722c717eebbafaf1b373f3edaadc;p=matthieu%2Fpve-network.git separate packaging and source build system like almost all of our repos do nowadays, modern git can detect such things on rebase so in development stuff should be hopefully not too much affected by this. Signed-off-by: Thomas Lamprecht --- diff --git a/Makefile b/Makefile index b4980de..d381be7 100644 --- a/Makefile +++ b/Makefile @@ -7,17 +7,16 @@ BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM) DEB=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION)_all.deb DSC=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION).dsc -all: - $(MAKE) -C PVE - .PHONY: dinstall dinstall: deb dpkg -i $(DEB) -$(BUILDDIR): PVE debian - rm -rf $(BUILDDIR) - rsync -a * $(BUILDDIR) - echo "git clone git://git.proxmox.com/git/pve-network.git\\ngit checkout $(shell git rev-parse HEAD)" > $(BUILDDIR)/debian/SOURCE +$(BUILDDIR): src debian + rm -rf $@ $@.tmp + cp -a src $@.tmp + cp -a debian $@.tmp/ + echo "git clone git://git.proxmox.com/git/pve-network.git\\ngit checkout $(shell git rev-parse HEAD)" > $@.tmp/debian/SOURCE + mv $@.tmp $@ .PHONY: deb deb: $(DEB) @@ -41,14 +40,6 @@ distclean: clean clean: rm -rf *~ *.deb *.changes $(PACKAGE)-[0-9]*/ $(PACKAGE)*.tar* *.build *.buildinfo *.dsc -.PHONY: test -test: - $(MAKE) -C test - -.PHONY: install -install: - $(MAKE) -C PVE install - .PHONY: upload upload: $(DEB) tar cf - $(DEB)|ssh -X repoman@repo.proxmox.com -- upload --product pve --dist bullseye diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile deleted file mode 100644 index 28b2830..0000000 --- a/PVE/API2/Makefile +++ /dev/null @@ -1,4 +0,0 @@ - -.PHONY: install -install: - make -C Network install diff --git a/PVE/API2/Network/Makefile b/PVE/API2/Network/Makefile deleted file mode 100644 index 396f79d..0000000 --- a/PVE/API2/Network/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -SOURCES=SDN.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/$$i; done - make -C SDN install diff --git a/PVE/API2/Network/SDN.pm b/PVE/API2/Network/SDN.pm deleted file mode 100644 index f129d60..0000000 --- a/PVE/API2/Network/SDN.pm +++ /dev/null @@ -1,144 +0,0 @@ -package PVE::API2::Network::SDN; - -use strict; -use warnings; - -use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file); -use PVE::Exception qw(raise_param_exc); -use PVE::JSONSchema qw(get_standard_option); -use PVE::RESTHandler; -use PVE::RPCEnvironment; -use PVE::SafeSyslog; -use PVE::Tools qw(run_command); -use PVE::Network::SDN; - -use PVE::API2::Network::SDN::Controllers; -use PVE::API2::Network::SDN::Vnets; -use PVE::API2::Network::SDN::Zones; -use PVE::API2::Network::SDN::Ipams; -use PVE::API2::Network::SDN::Dns; - -use base qw(PVE::RESTHandler); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Vnets", - path => 'vnets', -}); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Zones", - path => 'zones', -}); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Controllers", - path => 'controllers', -}); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Ipams", - path => 'ipams', -}); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Dns", - path => 'dns', -}); - -__PACKAGE__->register_method({ - name => 'index', - path => '', - method => 'GET', - description => "Directory index.", - permissions => { - check => ['perm', '/', [ 'SDN.Audit' ]], - }, - parameters => { - additionalProperties => 0, - properties => {}, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], - }, - code => sub { - my ($param) = @_; - - my $res = [ - { id => 'vnets' }, - { id => 'zones' }, - { id => 'controllers' }, - { id => 'ipams' }, - { id => 'dns' }, - ]; - - return $res; - }}); - -my $create_reload_network_worker = sub { - my ($nodename) = @_; - - # FIXME: how to proxy to final node ? - my $upid; - run_command(['pvesh', 'set', "/nodes/$nodename/network"], outfunc => sub { - my $line = shift; - if ($line =~ /^["']?(UPID:[^\s"']+)["']?$/) { - $upid = $1; - } - }); - #my $upid = PVE::API2::Network->reload_network_config(node => $nodename}); - my $res = PVE::Tools::upid_decode($upid); - - return $res->{pid}; -}; - -__PACKAGE__->register_method ({ - name => 'reload', - protected => 1, - path => '', - method => 'PUT', - description => "Apply sdn controller changes && reload.", - permissions => { - check => ['perm', '/sdn', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - }, - returns => { - type => 'string', - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - PVE::Network::SDN::commit_config(); - - my $code = sub { - $rpcenv->{type} = 'priv'; # to start tasks in background - PVE::Cluster::check_cfs_quorum(); - my $nodelist = PVE::Cluster::get_nodelist(); - for my $node (@$nodelist) { - my $pid = eval { $create_reload_network_worker->($node) }; - warn $@ if $@; - } - - # FIXME: use libpve-apiclient (like in cluster join) to create - # tasks and moitor the tasks. - - return; - }; - - return $rpcenv->fork_worker('reloadnetworkall', undef, $authuser, $code); - - }}); - - -1; diff --git a/PVE/API2/Network/SDN/Controllers.pm b/PVE/API2/Network/SDN/Controllers.pm deleted file mode 100644 index d8f18ab..0000000 --- a/PVE/API2/Network/SDN/Controllers.pm +++ /dev/null @@ -1,290 +0,0 @@ -package PVE::API2::Network::SDN::Controllers; - -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::Zones; -use PVE::Network::SDN::Controllers; -use PVE::Network::SDN::Controllers::Plugin; -use PVE::Network::SDN::Controllers::EvpnPlugin; -use PVE::Network::SDN::Controllers::BgpPlugin; -use PVE::Network::SDN::Controllers::FaucetPlugin; - -use Storable qw(dclone); -use PVE::JSONSchema qw(get_standard_option); -use PVE::RPCEnvironment; - -use PVE::RESTHandler; - -use base qw(PVE::RESTHandler); - -my $sdn_controllers_type_enum = PVE::Network::SDN::Controllers::Plugin->lookup_types(); - -my $api_sdn_controllers_config = sub { - my ($cfg, $id) = @_; - - my $scfg = dclone(PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id)); - $scfg->{controller} = $id; - $scfg->{digest} = $cfg->{digest}; - - return $scfg; -}; - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "SDN controllers index.", - permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/controllers/'", - user => 'all', - }, - parameters => { - additionalProperties => 0, - properties => { - type => { - description => "Only list sdn controllers of specific type", - type => 'string', - enum => $sdn_controllers_type_enum, - optional => 1, - }, - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { controller => { type => 'string' }, - type => { type => 'string' }, - state => { type => 'string', optional => 1 }, - pending => { optional => 1}, - }, - }, - links => [ { rel => 'child', href => "{controller}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - my $cfg = {}; - if($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Controllers::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{controllers}; - } else { - $cfg = PVE::Network::SDN::Controllers::config(); - } - - my @sids = PVE::Network::SDN::Controllers::sdn_controllers_ids($cfg); - my $res = []; - foreach my $id (@sids) { - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - next if !$rpcenv->check_any($authuser, "/sdn/controllers/$id", $privs, 1); - - my $scfg = &$api_sdn_controllers_config($cfg, $id); - next if $param->{type} && $param->{type} ne $scfg->{type}; - - my $plugin_config = $cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); - push @$res, $scfg; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ - name => 'read', - path => '{controller}', - method => 'GET', - description => "Read sdn controller configuration.", - permissions => { - check => ['perm', '/sdn/controllers/{controller}', ['SDN.Allocate']], - }, - - parameters => { - additionalProperties => 0, - properties => { - controller => get_standard_option('pve-sdn-controller-id'), - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { type => 'object' }, - code => sub { - my ($param) = @_; - - my $cfg = {}; - if($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Controllers::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{controllers}; - } else { - $cfg = PVE::Network::SDN::Controllers::config(); - } - - return &$api_sdn_controllers_config($cfg, $param->{controller}); - }}); - -__PACKAGE__->register_method ({ - name => 'create', - protected => 1, - path => '', - method => 'POST', - description => "Create a new sdn controller object.", - permissions => { - check => ['perm', '/sdn/controllers', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'controller'); - - my $plugin = PVE::Network::SDN::Controllers::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 $controller_cfg = PVE::Network::SDN::Controllers::config(); - - my $scfg = undef; - if ($scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id, 1)) { - die "sdn controller object ID '$id' already defined\n"; - } - - $controller_cfg->{ids}->{$id} = $opts; - $plugin->on_update_hook($id, $controller_cfg); - - PVE::Network::SDN::Controllers::write_config($controller_cfg); - - }, "create sdn controller object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'update', - protected => 1, - path => '{controller}', - method => 'PUT', - description => "Update sdn controller object configuration.", - permissions => { - check => ['perm', '/sdn/controllers', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'controller'); - my $digest = extract_param($param, 'digest'); - - PVE::Network::SDN::lock_sdn_config( - sub { - - my $controller_cfg = PVE::Network::SDN::Controllers::config(); - - PVE::SectionConfig::assert_if_modified($controller_cfg, $digest); - - my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id); - - my $plugin = PVE::Network::SDN::Controllers::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($id, $controller_cfg); - - PVE::Network::SDN::Controllers::write_config($controller_cfg); - - - }, "update sdn controller object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'delete', - protected => 1, - path => '{controller}', - method => 'DELETE', - description => "Delete sdn controller object configuration.", - permissions => { - check => ['perm', '/sdn/controllers', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - properties => { - controller => get_standard_option('pve-sdn-controller-id', { - completion => \&PVE::Network::SDN::Controllers::complete_sdn_controllers, - }), - }, - }, - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'controller'); - - PVE::Network::SDN::lock_sdn_config( - sub { - - my $cfg = PVE::Network::SDN::Controllers::config(); - - my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id); - - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); - - my $zone_cfg = PVE::Network::SDN::Zones::config(); - - $plugin->on_delete_hook($id, $zone_cfg); - - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Controllers::write_config($cfg); - - }, "delete sdn controller object failed"); - - - return undef; - }}); - -1; diff --git a/PVE/API2/Network/SDN/Dns.pm b/PVE/API2/Network/SDN/Dns.pm deleted file mode 100644 index 3d08552..0000000 --- a/PVE/API2/Network/SDN/Dns.pm +++ /dev/null @@ -1,242 +0,0 @@ -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/'", - 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($opts->{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/Ipams.pm b/PVE/API2/Network/SDN/Ipams.pm deleted file mode 100644 index 6410e8e..0000000 --- a/PVE/API2/Network/SDN/Ipams.pm +++ /dev/null @@ -1,248 +0,0 @@ -package PVE::API2::Network::SDN::Ipams; - -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::Ipams; -use PVE::Network::SDN::Ipams::Plugin; -use PVE::Network::SDN::Ipams::PVEPlugin; -use PVE::Network::SDN::Ipams::PhpIpamPlugin; -use PVE::Network::SDN::Ipams::NetboxPlugin; - -use Storable qw(dclone); -use PVE::JSONSchema qw(get_standard_option); -use PVE::RPCEnvironment; - -use PVE::RESTHandler; - -use base qw(PVE::RESTHandler); - -my $sdn_ipams_type_enum = PVE::Network::SDN::Ipams::Plugin->lookup_types(); - -my $api_sdn_ipams_config = sub { - my ($cfg, $id) = @_; - - my $scfg = dclone(PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id)); - $scfg->{ipam} = $id; - $scfg->{digest} = $cfg->{digest}; - - return $scfg; -}; - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "SDN ipams index.", - permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/ipams/'", - user => 'all', - }, - parameters => { - additionalProperties => 0, - properties => { - type => { - description => "Only list sdn ipams of specific type", - type => 'string', - enum => $sdn_ipams_type_enum, - optional => 1, - }, - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { ipam => { type => 'string'}, - type => { type => 'string'}, - }, - }, - links => [ { rel => 'child', href => "{ipam}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - - my $cfg = PVE::Network::SDN::Ipams::config(); - - my @sids = PVE::Network::SDN::Ipams::sdn_ipams_ids($cfg); - my $res = []; - foreach my $id (@sids) { - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - next if !$rpcenv->check_any($authuser, "/sdn/ipams/$id", $privs, 1); - - my $scfg = &$api_sdn_ipams_config($cfg, $id); - next if $param->{type} && $param->{type} ne $scfg->{type}; - - my $plugin_config = $cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - push @$res, $scfg; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ - name => 'read', - path => '{ipam}', - method => 'GET', - description => "Read sdn ipam configuration.", - permissions => { - check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']], - }, - - parameters => { - additionalProperties => 0, - properties => { - ipam => get_standard_option('pve-sdn-ipam-id'), - }, - }, - returns => { type => 'object' }, - code => sub { - my ($param) = @_; - - my $cfg = PVE::Network::SDN::Ipams::config(); - - return &$api_sdn_ipams_config($cfg, $param->{ipam}); - }}); - -__PACKAGE__->register_method ({ - name => 'create', - protected => 1, - path => '', - method => 'POST', - description => "Create a new sdn ipam object.", - permissions => { - check => ['perm', '/sdn/ipams', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'ipam'); - - my $plugin = PVE::Network::SDN::Ipams::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 $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $controller_cfg = PVE::Network::SDN::Controllers::config(); - - my $scfg = undef; - if ($scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id, 1)) { - die "sdn ipam object ID '$id' already defined\n"; - } - - $ipam_cfg->{ids}->{$id} = $opts; - - my $plugin_config = $opts; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - $plugin->on_update_hook($plugin_config); - - PVE::Network::SDN::Ipams::write_config($ipam_cfg); - - }, "create sdn ipam object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'update', - protected => 1, - path => '{ipam}', - method => 'PUT', - description => "Update sdn ipam object configuration.", - permissions => { - check => ['perm', '/sdn/ipams', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'ipam'); - my $digest = extract_param($param, 'digest'); - - PVE::Network::SDN::lock_sdn_config( - sub { - - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - - PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest); - - my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id); - - my $plugin = PVE::Network::SDN::Ipams::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::Ipams::write_config($ipam_cfg); - - }, "update sdn ipam object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'delete', - protected => 1, - path => '{ipam}', - method => 'DELETE', - description => "Delete sdn ipam object configuration.", - permissions => { - check => ['perm', '/sdn/ipams', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - properties => { - ipam => get_standard_option('pve-sdn-ipam-id', { - completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams, - }), - }, - }, - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'ipam'); - - PVE::Network::SDN::lock_sdn_config( - sub { - - my $cfg = PVE::Network::SDN::Ipams::config(); - - my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id); - - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); - - my $vnet_cfg = PVE::Network::SDN::Vnets::config(); - - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Ipams::write_config($cfg); - - }, "delete sdn zone object failed"); - - return undef; - }}); - -1; diff --git a/PVE/API2/Network/SDN/Makefile b/PVE/API2/Network/SDN/Makefile deleted file mode 100644 index 3683fa4..0000000 --- a/PVE/API2/Network/SDN/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/$$i; done - make -C Zones install - diff --git a/PVE/API2/Network/SDN/Subnets.pm b/PVE/API2/Network/SDN/Subnets.pm deleted file mode 100644 index 377a568..0000000 --- a/PVE/API2/Network/SDN/Subnets.pm +++ /dev/null @@ -1,303 +0,0 @@ -package PVE::API2::Network::SDN::Subnets; - -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::Exception qw(raise raise_param_exc); -use PVE::Network::SDN; -use PVE::Network::SDN::Subnets; -use PVE::Network::SDN::SubnetPlugin; -use PVE::Network::SDN::Vnets; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Ipams; -use PVE::Network::SDN::Ipams::Plugin; - -use Storable qw(dclone); -use PVE::JSONSchema qw(get_standard_option); -use PVE::RPCEnvironment; - -use PVE::RESTHandler; - -use base qw(PVE::RESTHandler); - -my $api_sdn_subnets_config = sub { - my ($cfg, $id) = @_; - - my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id)); - $scfg->{subnet} = $id; - $scfg->{digest} = $cfg->{digest}; - - return $scfg; -}; - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "SDN subnets index.", - permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/subnets/'", - user => 'all', - }, - parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id'), - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{subnet}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - my $vnetid = $param->{vnet}; - - my $cfg = {}; - if($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Subnets::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{subnets}; - } else { - $cfg = PVE::Network::SDN::Subnets::config(); - } - - my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); - my $res = []; - foreach my $id (@sids) { - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - next if !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid/subnets/$id", $privs, 1); - - my $scfg = &$api_sdn_subnets_config($cfg, $id); - next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid; - push @$res, $scfg; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ - name => 'read', - path => '{subnet}', - method => 'GET', - description => "Read sdn subnet configuration.", - permissions => { - check => ['perm', '/sdn/vnets/{vnet}/subnets/{subnet}', ['SDN.Allocate']], - }, - - parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id'), - subnet => get_standard_option('pve-sdn-subnet-id', { - completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, - }), - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { type => 'object' }, - code => sub { - my ($param) = @_; - - my $cfg = {}; - if($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Subnets::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{subnets}; - } else { - $cfg = PVE::Network::SDN::Subnets::config(); - } - - my $scfg = &$api_sdn_subnets_config($cfg, $param->{subnet}); - - raise_param_exc({ vnet => "wrong vnet"}) if $param->{vnet} ne $scfg->{vnet}; - - return $scfg; - }}); - -__PACKAGE__->register_method ({ - name => 'create', - protected => 1, - path => '', - method => 'POST', - description => "Create a new sdn subnet object.", - permissions => { - check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::SubnetPlugin->createSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $type = extract_param($param, 'type'); - my $cidr = extract_param($param, 'subnet'); - - # create /etc/pve/sdn directory - PVE::Cluster::check_cfs_quorum(); - mkdir("/etc/pve/sdn") if ! -d '/etc/pve/sdn'; - - PVE::Network::SDN::lock_sdn_config( - sub { - - my $cfg = PVE::Network::SDN::Subnets::config(); - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $vnet_cfg = PVE::Network::SDN::Vnets::config(); - my $vnet = $param->{vnet}; - my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; - my $zone = $zone_cfg->{ids}->{$zoneid}; - my $id = $cidr =~ s/\//-/r; - $id = "$zoneid-$id"; - - my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1); - - my $scfg = undef; - if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) { - die "sdn subnet object ID '$id' already defined\n"; - } - - $cfg->{ids}->{$id} = $opts; - - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); - PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet); - - PVE::Network::SDN::Subnets::write_config($cfg); - - }, "create sdn subnet object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'update', - protected => 1, - path => '{subnet}', - method => 'PUT', - description => "Update sdn subnet object configuration.", - permissions => { - check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'subnet'); - my $digest = extract_param($param, 'digest'); - - PVE::Network::SDN::lock_sdn_config( - sub { - - my $cfg = PVE::Network::SDN::Subnets::config(); - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $vnet_cfg = PVE::Network::SDN::Vnets::config(); - my $vnet = $param->{vnet}; - my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; - my $zone = $zone_cfg->{ids}->{$zoneid}; - - my $scfg = &$api_sdn_subnets_config($cfg, $id); - - PVE::SectionConfig::assert_if_modified($cfg, $digest); - - my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1); - $cfg->{ids}->{$id} = $opts; - - raise_param_exc({ ipam => "you can't change ipam"}) if $opts->{ipam} && $scfg->{ipam} && $opts->{ipam} ne $scfg->{ipam}; - - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); - PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg); - - PVE::Network::SDN::Subnets::write_config($cfg); - - }, "update sdn subnet object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'delete', - protected => 1, - path => '{subnet}', - method => 'DELETE', - description => "Delete sdn subnet object configuration.", - permissions => { - check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id'), - subnet => get_standard_option('pve-sdn-subnet-id', { - completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, - }), - }, - }, - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'subnet'); - - PVE::Network::SDN::lock_sdn_config( - sub { - my $cfg = PVE::Network::SDN::Subnets::config(); - - my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1); - - my $vnets_cfg = PVE::Network::SDN::Vnets::config(); - - PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg); - - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $vnet = $param->{vnet}; - my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone}; - my $zone = $zone_cfg->{ids}->{$zoneid}; - - PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg); - - delete $cfg->{ids}->{$id}; - - PVE::Network::SDN::Subnets::write_config($cfg); - - }, "delete sdn subnet object failed"); - - - return undef; - }}); - -1; diff --git a/PVE/API2/Network/SDN/Vnets.pm b/PVE/API2/Network/SDN/Vnets.pm deleted file mode 100644 index 811a2e8..0000000 --- a/PVE/API2/Network/SDN/Vnets.pm +++ /dev/null @@ -1,292 +0,0 @@ -package PVE::API2::Network::SDN::Vnets; - -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::Zones; -use PVE::Network::SDN::Zones::Plugin; -use PVE::Network::SDN::Vnets; -use PVE::Network::SDN::VnetPlugin; -use PVE::Network::SDN::Subnets; -use PVE::API2::Network::SDN::Subnets; - -use Storable qw(dclone); -use PVE::JSONSchema qw(get_standard_option); -use PVE::RPCEnvironment; -use PVE::Exception qw(raise raise_param_exc); - -use PVE::RESTHandler; - -use base qw(PVE::RESTHandler); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Subnets", - path => '{vnet}/subnets', -}); - -my $api_sdn_vnets_config = sub { - my ($cfg, $id) = @_; - - my $scfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id)); - $scfg->{vnet} = $id; - $scfg->{digest} = $cfg->{digest}; - - return $scfg; -}; - -my $api_sdn_vnets_deleted_config = sub { - my ($cfg, $running_cfg, $id) = @_; - - if (!$cfg->{ids}->{$id}) { - - my $vnet_cfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($running_cfg->{vnets}, $id)); - $vnet_cfg->{state} = "deleted"; - $vnet_cfg->{vnet} = $id; - return $vnet_cfg; - } -}; - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "SDN vnets index.", - permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'" - ." permissions on '/sdn/vnets/'", - user => 'all', - }, - parameters => { - additionalProperties => 0, - properties => { - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{vnet}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - my $cfg = {}; - if($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Vnets::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{vnets}; - } else { - $cfg = PVE::Network::SDN::Vnets::config(); - } - - my @sids = PVE::Network::SDN::Vnets::sdn_vnets_ids($cfg); - my $res = []; - foreach my $id (@sids) { - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - next if !$rpcenv->check_any($authuser, "/sdn/vnets/$id", $privs, 1); - - my $scfg = &$api_sdn_vnets_config($cfg, $id); - push @$res, $scfg; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ - name => 'read', - path => '{vnet}', - method => 'GET', - description => "Read sdn vnet configuration.", - permissions => { - check => ['perm', '/sdn/vnets/{vnet}', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id', { - completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, - }), - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { type => 'object' }, - code => sub { - my ($param) = @_; - - my $cfg = {}; - if($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Vnets::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{vnets}; - } else { - $cfg = PVE::Network::SDN::Vnets::config(); - } - - return $api_sdn_vnets_config->($cfg, $param->{vnet}); - }}); - -__PACKAGE__->register_method ({ - name => 'create', - protected => 1, - path => '', - method => 'POST', - description => "Create a new sdn vnet object.", - permissions => { - check => ['perm', '/sdn/vnets', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::VnetPlugin->createSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'vnet'); - - PVE::Cluster::check_cfs_quorum(); - mkdir("/etc/pve/sdn"); - - PVE::Network::SDN::lock_sdn_config(sub { - my $cfg = PVE::Network::SDN::Vnets::config(); - my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 1, 1); - - if (PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id, 1)) { - die "sdn vnet object ID '$id' already defined\n"; - } - $cfg->{ids}->{$id} = $opts; - - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $zoneid = $cfg->{ids}->{$id}->{zone}; - my $plugin_config = $zone_cfg->{ids}->{$zoneid}; - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - $plugin->vnet_update_hook($cfg, $id, $zone_cfg); - - PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); - - PVE::Network::SDN::Vnets::write_config($cfg); - - }, "create sdn vnet object failed"); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'update', - protected => 1, - path => '{vnet}', - method => 'PUT', - description => "Update sdn vnet object configuration.", - permissions => { - check => ['perm', '/sdn/vnets', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::VnetPlugin->updateSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'vnet'); - my $digest = extract_param($param, 'digest'); - - PVE::Network::SDN::lock_sdn_config(sub { - my $cfg = PVE::Network::SDN::Vnets::config(); - - PVE::SectionConfig::assert_if_modified($cfg, $digest); - - - my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1); - raise_param_exc({ zone => "missing zone"}) if !$opts->{zone}; - my $subnets = PVE::Network::SDN::Vnets::get_subnets($id); - raise_param_exc({ zone => "can't change zone if subnets exists"}) if($subnets && $opts->{zone} ne $cfg->{ids}->{$id}->{zone}); - - $cfg->{ids}->{$id} = $opts; - - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $zoneid = $cfg->{ids}->{$id}->{zone}; - my $plugin_config = $zone_cfg->{ids}->{$zoneid}; - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - $plugin->vnet_update_hook($cfg, $id, $zone_cfg); - - PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); - - PVE::Network::SDN::Vnets::write_config($cfg); - - }, "update sdn vnet object failed"); - - return undef; - } -}); - -__PACKAGE__->register_method ({ - name => 'delete', - protected => 1, - path => '{vnet}', - method => 'DELETE', - description => "Delete sdn vnet object configuration.", - permissions => { - check => ['perm', '/sdn/vnets', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id', { - completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, - }), - }, - }, - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'vnet'); - - PVE::Network::SDN::lock_sdn_config(sub { - my $cfg = PVE::Network::SDN::Vnets::config(); - my $scfg = PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id); # check if exists - my $vnet_cfg = PVE::Network::SDN::Vnets::config(); - - PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg); - - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Vnets::write_config($cfg); - - }, "delete sdn vnet object failed"); - - - return undef; - } -}); - -1; diff --git a/PVE/API2/Network/SDN/Zones.pm b/PVE/API2/Network/SDN/Zones.pm deleted file mode 100644 index 6e53240..0000000 --- a/PVE/API2/Network/SDN/Zones.pm +++ /dev/null @@ -1,351 +0,0 @@ -package PVE::API2::Network::SDN::Zones; - -use strict; -use warnings; - -use Storable qw(dclone); - -use PVE::Cluster qw(cfs_read_file cfs_write_file); -use PVE::Exception qw(raise raise_param_exc); -use PVE::JSONSchema qw(get_standard_option); -use PVE::RPCEnvironment; -use PVE::SafeSyslog; -use PVE::Tools qw(extract_param); - -use PVE::Network::SDN::Dns; -use PVE::Network::SDN::Subnets; -use PVE::Network::SDN::Vnets; -use PVE::Network::SDN; - -use PVE::Network::SDN::Zones::EvpnPlugin; -use PVE::Network::SDN::Zones::FaucetPlugin; -use PVE::Network::SDN::Zones::Plugin; -use PVE::Network::SDN::Zones::QinQPlugin; -use PVE::Network::SDN::Zones::SimplePlugin; -use PVE::Network::SDN::Zones::VlanPlugin; -use PVE::Network::SDN::Zones::VxlanPlugin; -use PVE::Network::SDN::Zones; - -use PVE::RESTHandler; -use base qw(PVE::RESTHandler); - -my $sdn_zones_type_enum = PVE::Network::SDN::Zones::Plugin->lookup_types(); - -my $api_sdn_zones_config = sub { - my ($cfg, $id) = @_; - - my $scfg = dclone(PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id)); - $scfg->{zone} = $id; - $scfg->{digest} = $cfg->{digest}; - - if ($scfg->{nodes}) { - $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $scfg->{nodes}); - } - - if ($scfg->{exitnodes}) { - $scfg->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $scfg->{exitnodes}); - } - - my $pending = $scfg->{pending}; - if ($pending->{nodes}) { - $pending->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $pending->{nodes}); - } - - if ($pending->{exitnodes}) { - $pending->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes}); - } - - return $scfg; -}; - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "SDN zones index.", - permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/'", - user => 'all', - }, - parameters => { - additionalProperties => 0, - properties => { - type => { - description => "Only list SDN zones of specific type", - type => 'string', - enum => $sdn_zones_type_enum, - optional => 1, - }, - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { zone => { type => 'string'}, - type => { type => 'string'}, - mtu => { type => 'integer', optional => 1 }, - dns => { type => 'string', optional => 1}, - reversedns => { type => 'string', optional => 1}, - dnszone => { type => 'string', optional => 1}, - ipam => { type => 'string', optional => 1}, - pending => { optional => 1}, - state => { type => 'string', optional => 1}, - nodes => { type => 'string', optional => 1}, - }, - }, - links => [ { rel => 'child', href => "{zone}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - my $cfg = {}; - if ($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Zones::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{zones}; - } else { - $cfg = PVE::Network::SDN::Zones::config(); - } - - my @sids = PVE::Network::SDN::Zones::sdn_zones_ids($cfg); - my $res = []; - for my $id (@sids) { - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - next if !$rpcenv->check_any($authuser, "/sdn/zones/$id", $privs, 1); - - my $scfg = &$api_sdn_zones_config($cfg, $id); - next if $param->{type} && $param->{type} ne $scfg->{type}; - - my $plugin_config = $cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - push @$res, $scfg; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ - name => 'read', - path => '{zone}', - method => 'GET', - description => "Read sdn zone configuration.", - permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], - }, - - parameters => { - additionalProperties => 0, - properties => { - zone => get_standard_option('pve-sdn-zone-id'), - running => { - type => 'boolean', - optional => 1, - description => "Display running config.", - }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - } - }, - }, - returns => { type => 'object' }, - code => sub { - my ($param) = @_; - - my $cfg = {}; - if ($param->{pending}) { - my $running_cfg = PVE::Network::SDN::running_config(); - my $config = PVE::Network::SDN::Zones::config(); - $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones'); - } elsif ($param->{running}) { - my $running_cfg = PVE::Network::SDN::running_config(); - $cfg = $running_cfg->{zones}; - } else { - $cfg = PVE::Network::SDN::Zones::config(); - } - - return &$api_sdn_zones_config($cfg, $param->{zone}); - }}); - -__PACKAGE__->register_method ({ - name => 'create', - protected => 1, - path => '', - method => 'POST', - description => "Create a new sdn zone object.", - permissions => { - check => ['perm', '/sdn/zones', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::Zones::Plugin->createSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'zone'); - - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type); - my $opts = $plugin->check_config($id, $param, 1, 1); - - PVE::Cluster::check_cfs_quorum(); - mkdir("/etc/pve/sdn"); - - PVE::Network::SDN::lock_sdn_config(sub { - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $controller_cfg = PVE::Network::SDN::Controllers::config(); - my $dns_cfg = PVE::Network::SDN::Dns::config(); - - my $scfg = undef; - if ($scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id, 1)) { - die "sdn zone object ID '$id' already defined\n"; - } - - my $dnsserver = $opts->{dns}; - raise_param_exc({ dns => "$dnsserver don't exist"}) - if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; - - my $reversednsserver = $opts->{reversedns}; - raise_param_exc({ reversedns => "$reversednsserver don't exist"}) - if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver}; - - my $dnszone = $opts->{dnszone}; - raise_param_exc({ dnszone => "missing dns server"}) - if $dnszone && !$dnsserver; - - my $ipam = $opts->{ipam}; - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam}; - - $zone_cfg->{ids}->{$id} = $opts; - $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); - - PVE::Network::SDN::Zones::write_config($zone_cfg); - - }, "create sdn zone object failed"); - - return; - }}); - -__PACKAGE__->register_method ({ - name => 'update', - protected => 1, - path => '{zone}', - method => 'PUT', - description => "Update sdn zone object configuration.", - permissions => { - check => ['perm', '/sdn/zones', ['SDN.Allocate']], - }, - parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(), - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'zone'); - my $digest = extract_param($param, 'digest'); - - PVE::Network::SDN::lock_sdn_config(sub { - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $controller_cfg = PVE::Network::SDN::Controllers::config(); - my $dns_cfg = PVE::Network::SDN::Dns::config(); - - PVE::SectionConfig::assert_if_modified($zone_cfg, $digest); - - my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id); - - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); - my $opts = $plugin->check_config($id, $param, 0, 1); - - if ($opts->{ipam} && !$scfg->{ipam} || $opts->{ipam} ne $scfg->{ipam}) { - - # don't allow ipam change if subnet are defined for now, need to implement resync ipam content - my $subnets_cfg = PVE::Network::SDN::Subnets::config(); - for my $subnetid (sort keys %{$subnets_cfg->{ids}}) { - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); - raise_param_exc({ ipam => "can't change ipam if a subnet is already defined in this zone"}) - if $subnet->{zone} eq $id; - } - } - - $zone_cfg->{ids}->{$id} = $opts; - - my $dnsserver = $opts->{dns}; - raise_param_exc({ dns => "$dnsserver don't exist"}) if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; - - my $reversednsserver = $opts->{reversedns}; - raise_param_exc({ reversedns => "$reversednsserver don't exist"}) if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver}; - - my $dnszone = $opts->{dnszone}; - raise_param_exc({ dnszone => "missing dns server"}) if $dnszone && !$dnsserver; - - my $ipam = $opts->{ipam}; - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam}; - - $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); - - PVE::Network::SDN::Zones::write_config($zone_cfg); - - }, "update sdn zone object failed"); - - return; - }}); - -__PACKAGE__->register_method ({ - name => 'delete', - protected => 1, - path => '{zone}', - method => 'DELETE', - description => "Delete sdn zone object configuration.", - permissions => { - check => ['perm', '/sdn/zones', ['SDN.Allocate']], - }, - parameters => { - additionalProperties => 0, - properties => { - zone => get_standard_option('pve-sdn-zone-id', { - completion => \&PVE::Network::SDN::Zones::complete_sdn_zones, - }), - }, - }, - returns => { type => 'null' }, - code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'zone'); - - PVE::Network::SDN::lock_sdn_config(sub { - my $cfg = PVE::Network::SDN::Zones::config(); - my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id); - - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); - my $vnet_cfg = PVE::Network::SDN::Vnets::config(); - - $plugin->on_delete_hook($id, $vnet_cfg); - - delete $cfg->{ids}->{$id}; - - PVE::Network::SDN::Zones::write_config($cfg); - }, "delete sdn zone object failed"); - - return; - }}); - -1; diff --git a/PVE/API2/Network/SDN/Zones/Content.pm b/PVE/API2/Network/SDN/Zones/Content.pm deleted file mode 100644 index 66f49df..0000000 --- a/PVE/API2/Network/SDN/Zones/Content.pm +++ /dev/null @@ -1,85 +0,0 @@ -package PVE::API2::Network::SDN::Zones::Content; - -use strict; -use warnings; -use Data::Dumper; - -use PVE::SafeSyslog; -use PVE::Cluster; -use PVE::INotify; -use PVE::Exception qw(raise_param_exc); -use PVE::RPCEnvironment; -use PVE::RESTHandler; -use PVE::JSONSchema qw(get_standard_option); -use PVE::Network::SDN; - -use base qw(PVE::RESTHandler); - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "List zone content.", - permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], - }, - protected => 1, - proxyto => 'node', - parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - zone => get_standard_option('pve-sdn-zone-id', { - completion => \&PVE::Network::SDN::Zones::complete_sdn_zone, - }), - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { - vnet => { - description => "Vnet identifier.", - type => 'string', - }, - status => { - description => "Status.", - type => 'string', - optional => 1, - }, - statusmsg => { - description => "Status details", - type => 'string', - optional => 1, - }, - }, - }, - links => [ { rel => 'child', href => "{vnet}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - - my $authuser = $rpcenv->get_user(); - - my $zoneid = $param->{zone}; - - my $res = []; - - my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); - - foreach my $id (keys %{$vnet_status}) { - if ($vnet_status->{$id}->{zone} eq $zoneid) { - my $item->{vnet} = $id; - $item->{status} = $vnet_status->{$id}->{'status'}; - $item->{statusmsg} = $vnet_status->{$id}->{'statusmsg'}; - push @$res,$item; - } - } - - return $res; - }}); - -1; diff --git a/PVE/API2/Network/SDN/Zones/Makefile b/PVE/API2/Network/SDN/Zones/Makefile deleted file mode 100644 index 9b0a42b..0000000 --- a/PVE/API2/Network/SDN/Zones/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -SOURCES=Status.pm Content.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/Zones/$$i; done diff --git a/PVE/API2/Network/SDN/Zones/Status.pm b/PVE/API2/Network/SDN/Zones/Status.pm deleted file mode 100644 index 17de68f..0000000 --- a/PVE/API2/Network/SDN/Zones/Status.pm +++ /dev/null @@ -1,111 +0,0 @@ -package PVE::API2::Network::SDN::Zones::Status; - -use strict; -use warnings; - -use File::Path; -use File::Basename; -use PVE::Tools; -use PVE::INotify; -use PVE::Cluster; -use PVE::API2::Network::SDN::Zones::Content; -use PVE::RESTHandler; -use PVE::RPCEnvironment; -use PVE::JSONSchema qw(get_standard_option); -use PVE::Exception qw(raise_param_exc); - -use base qw(PVE::RESTHandler); - -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Zones::Content", - path => '{zone}/content', -}); - -__PACKAGE__->register_method ({ - name => 'index', - path => '', - method => 'GET', - description => "Get status for all zones.", - permissions => { - description => "Only list entries where you have 'SDN.Audit'", - user => 'all', - }, - protected => 1, - proxyto => 'node', - parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node') - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { - zone => get_standard_option('pve-sdn-zone-id'), - status => { - description => "Status of zone", - type => 'string', - enum => ['available', 'pending', 'error'], - }, - }, - }, - links => [ { rel => 'child', href => "{zone}" } ], - }, - code => sub { - my ($param) = @_; - - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - my $localnode = PVE::INotify::nodename(); - - my $res = []; - - my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); - - foreach my $id (sort keys %{$zone_status}) { - my $item->{zone} = $id; - $item->{status} = $zone_status->{$id}->{'status'}; - push @$res, $item; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ - name => 'diridx', - path => '{zone}', - method => 'GET', - description => "", - permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], - }, - parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - zone => get_standard_option('pve-sdn-zone-id'), - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => { - subdir => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{subdir}" } ], - }, - code => sub { - my ($param) = @_; - my $res = [ - { subdir => 'content' }, - ]; - - return $res; - }}); - -1; diff --git a/PVE/Makefile b/PVE/Makefile deleted file mode 100644 index 26e01a4..0000000 --- a/PVE/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -all: - -.PHONY: install -install: - make -C Network install - make -C API2 install diff --git a/PVE/Network/Makefile b/PVE/Network/Makefile deleted file mode 100644 index 277e19c..0000000 --- a/PVE/Network/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -SOURCES=SDN.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/$$i; done - make -C SDN install diff --git a/PVE/Network/SDN.pm b/PVE/Network/SDN.pm deleted file mode 100644 index b95dd5b..0000000 --- a/PVE/Network/SDN.pm +++ /dev/null @@ -1,283 +0,0 @@ -package PVE::Network::SDN; - -use strict; -use warnings; - -use Data::Dumper; -use JSON; - -use PVE::Network::SDN::Vnets; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use PVE::Network::SDN::Subnets; - -use PVE::Tools qw(extract_param dir_glob_regex run_command); -use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); - - -my $running_cfg = "sdn/.running-config"; - -my $parse_running_cfg = sub { - my ($filename, $raw) = @_; - - my $cfg = {}; - - return $cfg if !defined($raw) || $raw eq ''; - - eval { - $cfg = from_json($raw); - }; - return {} if $@; - - return $cfg; -}; - -my $write_running_cfg = sub { - my ($filename, $cfg) = @_; - - my $json = to_json($cfg); - - return $json; -}; - -PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running_cfg); - - -# improve me : move status code inside plugins ? - -sub ifquery_check { - - my $cmd = ['ifquery', '-a', '-c', '-o','json']; - - my $result = ''; - my $reader = sub { $result .= shift }; - - eval { - run_command($cmd, outfunc => $reader); - }; - - my $resultjson = decode_json($result); - my $interfaces = {}; - - foreach my $interface (@$resultjson) { - my $name = $interface->{name}; - $interfaces->{$name} = { - status => $interface->{status}, - config => $interface->{config}, - config_status => $interface->{config_status}, - }; - } - - return $interfaces; -} - -sub status { - - my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status(); - return($zone_status, $vnet_status); -} - -sub running_config { - return cfs_read_file($running_cfg); -} - -sub pending_config { - my ($running_cfg, $cfg, $type) = @_; - - my $pending = {}; - - my $running_objects = $running_cfg->{$type}->{ids}; - my $config_objects = $cfg->{ids}; - - foreach my $id (sort keys %{$running_objects}) { - my $running_object = $running_objects->{$id}; - my $config_object = $config_objects->{$id}; - foreach my $key (sort keys %{$running_object}) { - $pending->{$id}->{$key} = $running_object->{$key}; - if(!keys %{$config_object}) { - $pending->{$id}->{state} = "deleted"; - } elsif (!defined($config_object->{$key})) { - $pending->{$id}->{"pending"}->{$key} = 'deleted'; - $pending->{$id}->{state} = "changed"; - } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) - ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) { - $pending->{$id}->{state} = "changed"; - } - } - $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"}); - } - - foreach my $id (sort keys %{$config_objects}) { - my $running_object = $running_objects->{$id}; - my $config_object = $config_objects->{$id}; - - foreach my $key (sort keys %{$config_object}) { - my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key}; - my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key}; - if($key eq 'type' || $key eq 'vnet') { - $pending->{$id}->{$key} = $config_value; - } else { - $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value); - } - if(!keys %{$running_object}) { - $pending->{$id}->{state} = "new"; - } elsif (!defined($running_value) && defined($config_value)) { - $pending->{$id}->{state} = "changed"; - } - } - $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"}); - } - - return {ids => $pending}; - -} - -sub commit_config { - - my $cfg = cfs_read_file($running_cfg); - my $version = $cfg->{version}; - - if ($version) { - $version++; - } else { - $version = 1; - } - - my $vnets_cfg = PVE::Network::SDN::Vnets::config(); - my $zones_cfg = PVE::Network::SDN::Zones::config(); - my $controllers_cfg = PVE::Network::SDN::Controllers::config(); - my $subnets_cfg = PVE::Network::SDN::Subnets::config(); - - my $vnets = { ids => $vnets_cfg->{ids} }; - my $zones = { ids => $zones_cfg->{ids} }; - my $controllers = { ids => $controllers_cfg->{ids} }; - my $subnets = { ids => $subnets_cfg->{ids} }; - - $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets }; - - cfs_write_file($running_cfg, $cfg); -} - -sub lock_sdn_config { - my ($code, $errmsg) = @_; - - cfs_lock_file($running_cfg, undef, $code); - - if (my $err = $@) { - $errmsg ? die "$errmsg: $err" : die $err; - } -} - -sub get_local_vnets { - - my $rpcenv = PVE::RPCEnvironment::get(); - - my $authuser = $rpcenv->get_user(); - - my $nodename = PVE::INotify::nodename(); - - my $cfg = PVE::Network::SDN::running_config(); - my $vnets_cfg = $cfg->{vnets}; - my $zones_cfg = $cfg->{zones}; - - my @vnetids = PVE::Network::SDN::Vnets::sdn_vnets_ids($vnets_cfg); - - my $vnets = {}; - - foreach my $vnetid (@vnetids) { - - my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid); - my $zoneid = $vnet->{zone}; - my $comments = $vnet->{alias}; - - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - - next if !$zoneid; - next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid", $privs, 1) && !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid", $privs, 1); - - my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid); - - next if defined($zone_config->{nodes}) && !$zone_config->{nodes}->{$nodename}; - my $ipam = $zone_config->{ipam} ? 1 : 0; - my $vlanaware = $vnet->{vlanaware} ? 1 : 0; - $vnets->{$vnetid} = { type => 'vnet', active => '1', ipam => $ipam, vlanaware => $vlanaware, comments => $comments }; - } - - return $vnets; -} - -sub generate_zone_config { - my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config(); - PVE::Network::SDN::Zones::write_etc_network_config($raw_config); -} - -sub generate_controller_config { - my ($reload) = @_; - - my $raw_config = PVE::Network::SDN::Controllers::generate_controller_config(); - PVE::Network::SDN::Controllers::write_controller_config($raw_config); - - PVE::Network::SDN::Controllers::reload_controller() if $reload; -} - -sub encode_value { - my ($type, $key, $value) = @_; - - if ($key eq 'nodes' || $key eq 'exitnodes') { - if(ref($value) eq 'HASH') { - return join(',', sort keys(%$value)); - } else { - return $value; - } - } - - return $value; -} - - -#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 if $raw eq ''; - - my $json = ''; - eval { - $json = from_json($raw); - }; - die "api response is not a json" if $@; - - return $json; -} - -1; diff --git a/PVE/Network/SDN/Controllers.pm b/PVE/Network/SDN/Controllers.pm deleted file mode 100644 index a23048e..0000000 --- a/PVE/Network/SDN/Controllers.pm +++ /dev/null @@ -1,181 +0,0 @@ -package PVE::Network::SDN::Controllers; - -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::SDN::Vnets; -use PVE::Network::SDN::Zones; - -use PVE::Network::SDN::Controllers::EvpnPlugin; -use PVE::Network::SDN::Controllers::BgpPlugin; -use PVE::Network::SDN::Controllers::FaucetPlugin; -use PVE::Network::SDN::Controllers::Plugin; -PVE::Network::SDN::Controllers::EvpnPlugin->register(); -PVE::Network::SDN::Controllers::BgpPlugin->register(); -PVE::Network::SDN::Controllers::FaucetPlugin->register(); -PVE::Network::SDN::Controllers::Plugin->init(); - - -sub sdn_controllers_config { - my ($cfg, $id, $noerr) = @_; - - die "no sdn controller 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/controllers.cfg"); - $config = cfs_read_file("sdn/controllers.cfg") if !keys %{$config->{ids}}; - return $config; -} - -sub write_config { - my ($cfg) = @_; - - cfs_write_file("sdn/controllers.cfg", $cfg); -} - -sub lock_sdn_controllers_config { - my ($code, $errmsg) = @_; - - cfs_lock_file("sdn/controllers.cfg", undef, $code); - if (my $err = $@) { - $errmsg ? die "$errmsg: $err" : die $err; - } -} - -sub sdn_controllers_ids { - my ($cfg) = @_; - - return sort keys %{$cfg->{ids}}; -} - -sub complete_sdn_controller { - my ($cmdname, $pname, $cvalue) = @_; - - my $cfg = PVE::Network::SDN::running_config(); - - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_controllers_ids($cfg) ]; -} - -sub generate_controller_config { - - my $cfg = PVE::Network::SDN::running_config(); - my $vnet_cfg = $cfg->{vnets}; - my $zone_cfg = $cfg->{zones}; - my $controller_cfg = $cfg->{controllers}; - - return if !$vnet_cfg && !$zone_cfg && !$controller_cfg; - - #read main config for physical interfaces - my $current_config_file = "/etc/network/interfaces"; - my $fh = IO::File->new($current_config_file); - my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh); - $fh->close(); - - # check uplinks - my $uplinks = {}; - foreach my $id (keys %{$interfaces_config->{ifaces}}) { - my $interface = $interfaces_config->{ifaces}->{$id}; - if (my $uplink = $interface->{'uplink-id'}) { - die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink}; - $interface->{name} = $id; - $uplinks->{$interface->{'uplink-id'}} = $interface; - } - } - - # generate configuration - my $config = {}; - - foreach my $id (sort keys %{$controller_cfg->{ids}}) { - my $plugin_config = $controller_cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); - $plugin->generate_controller_config($plugin_config, $controller_cfg, $id, $uplinks, $config); - } - - foreach my $id (sort keys %{$zone_cfg->{ids}}) { - my $plugin_config = $zone_cfg->{ids}->{$id}; - my $controllerid = $plugin_config->{controller}; - next if !$controllerid; - my $controller = $controller_cfg->{ids}->{$controllerid}; - if ($controller) { - my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type}); - $controller_plugin->generate_controller_zone_config($plugin_config, $controller, $controller_cfg, $id, $uplinks, $config); - } - } - - foreach my $id (sort keys %{$vnet_cfg->{ids}}) { - my $plugin_config = $vnet_cfg->{ids}->{$id}; - my $zoneid = $plugin_config->{zone}; - next if !$zoneid; - my $zone = $zone_cfg->{ids}->{$zoneid}; - next if !$zone; - my $controllerid = $zone->{controller}; - next if !$controllerid; - my $controller = $controller_cfg->{ids}->{$controllerid}; - if ($controller) { - my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type}); - $controller_plugin->generate_controller_vnet_config($plugin_config, $controller, $zone, $zoneid, $id, $config); - } - } - - return $config; -} - - -sub reload_controller { - - my $cfg = PVE::Network::SDN::running_config(); - my $controller_cfg = $cfg->{controllers}; - - return if !$controller_cfg; - - foreach my $id (keys %{$controller_cfg->{ids}}) { - my $plugin_config = $controller_cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); - $plugin->reload_controller(); - } -} - -sub generate_controller_rawconfig { - my ($config) = @_; - - my $cfg = PVE::Network::SDN::running_config(); - my $controller_cfg = $cfg->{controllers}; - return if !$controller_cfg; - - my $rawconfig = ""; - foreach my $id (keys %{$controller_cfg->{ids}}) { - my $plugin_config = $controller_cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); - $rawconfig .= $plugin->generate_controller_rawconfig($plugin_config, $config); - } - return $rawconfig; -} - -sub write_controller_config { - my ($config) = @_; - - my $cfg = PVE::Network::SDN::running_config(); - my $controller_cfg = $cfg->{controllers}; - return if !$controller_cfg; - - foreach my $id (keys %{$controller_cfg->{ids}}) { - my $plugin_config = $controller_cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); - $plugin->write_controller_config($plugin_config, $config); - } -} - -1; - diff --git a/PVE/Network/SDN/Controllers/BgpPlugin.pm b/PVE/Network/SDN/Controllers/BgpPlugin.pm deleted file mode 100644 index 0b8cf1a..0000000 --- a/PVE/Network/SDN/Controllers/BgpPlugin.pm +++ /dev/null @@ -1,183 +0,0 @@ -package PVE::Network::SDN::Controllers::BgpPlugin; - -use strict; -use warnings; - -use PVE::INotify; -use PVE::JSONSchema qw(get_standard_option); -use PVE::Tools qw(run_command file_set_contents file_get_contents); - -use PVE::Network::SDN::Controllers::Plugin; -use PVE::Network::SDN::Zones::Plugin; -use Net::IP; - -use base('PVE::Network::SDN::Controllers::Plugin'); - -sub type { - return 'bgp'; -} - -sub properties { - return { - 'bgp-multipath-as-path-relax' => { - type => 'boolean', - optional => 1, - }, - ebgp => { - type => 'boolean', - optional => 1, - description => "Enable ebgp. (remote-as external)", - }, - 'ebgp-multihop' => { - type => 'integer', - optional => 1, - }, - loopback => { - description => "source loopback interface.", - type => 'string' - }, - node => get_standard_option('pve-node'), - }; -} - -sub options { - return { - 'node' => { optional => 0 }, - 'asn' => { optional => 0 }, - 'peers' => { optional => 0 }, - 'bgp-multipath-as-path-relax' => { optional => 1 }, - 'ebgp' => { optional => 1 }, - 'ebgp-multihop' => { optional => 1 }, - 'loopback' => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_controller_config { - my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_; - - my @peers; - @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; - - my $asn = $plugin_config->{asn}; - my $ebgp = $plugin_config->{ebgp}; - my $ebgp_multihop = $plugin_config->{'ebgp-multihop'}; - my $loopback = $plugin_config->{loopback}; - my $multipath_relax = $plugin_config->{'bgp-multipath-as-path-relax'}; - - my $local_node = PVE::INotify::nodename(); - - - return if !$asn; - return if $local_node ne $plugin_config->{node}; - - my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {}; - - my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); - - my $remoteas = $ebgp ? "external" : $asn; - - #global options - my @controller_config = ( - "bgp router-id $ifaceip", - "no bgp default ipv4-unicast", - "coalesce-time 1000" - ); - - push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0; - - @controller_config = (); - if($ebgp) { - push @controller_config, "bgp disable-ebgp-connected-route-check" if $loopback; - } - - push @controller_config, "bgp bestpath as-path multipath-relax" if $multipath_relax; - - #BGP neighbors - if(@peers) { - push @controller_config, "neighbor BGP peer-group"; - push @controller_config, "neighbor BGP remote-as $remoteas"; - push @controller_config, "neighbor BGP bfd"; - push @controller_config, "neighbor BGP ebgp-multihop $ebgp_multihop" if $ebgp && $ebgp_multihop; - } - - # BGP peers - foreach my $address (@peers) { - push @controller_config, "neighbor $address peer-group BGP"; - } - push(@{$bgp->{""}}, @controller_config); - - # address-family unicast - if (@peers) { - my $ipversion = Net::IP::ip_is_ipv6($ifaceip) ? "ipv6" : "ipv4"; - my $mask = Net::IP::ip_is_ipv6($ifaceip) ? "/128" : "32"; - - push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "network $ifaceip/$mask") if $loopback; - push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP activate"); - push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP soft-reconfiguration inbound"); - } - - if ($loopback) { - $config->{frr_prefix_list}->{loopbacks_ips}->{10} = "permit 0.0.0.0/0 le 32"; - push(@{$config->{frr}->{''}}, "ip protocol bgp route-map correct_src"); - - my $routemap_config = (); - push @{$routemap_config}, "match ip address prefix-list loopbacks_ips"; - push @{$routemap_config}, "set src $ifaceip"; - my $routemap = { rule => $routemap_config, action => "permit" }; - push(@{$config->{frr_routemap}->{'correct_src'}}, $routemap); - } - - return $config; -} - -sub generate_controller_zone_config { - my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; - -} - -sub on_delete_hook { - my ($class, $controllerid, $zone_cfg) = @_; - - # verify that zone is associated to this controller - foreach my $id (keys %{$zone_cfg->{ids}}) { - my $zone = $zone_cfg->{ids}->{$id}; - die "controller $controllerid is used by $id" - if (defined($zone->{controller}) && $zone->{controller} eq $controllerid); - } -} - -sub on_update_hook { - my ($class, $controllerid, $controller_cfg) = @_; - - # we can only have 1 bgp controller by node - my $local_node = PVE::INotify::nodename(); - my $controllernb = 0; - foreach my $id (keys %{$controller_cfg->{ids}}) { - next if $id eq $controllerid; - my $controller = $controller_cfg->{ids}->{$id}; - next if $controller->{type} ne "bgp"; - next if $controller->{node} ne $local_node; - $controllernb++; - die "only 1 bgp controller can be defined" if $controllernb > 1; - } -} - -sub generate_controller_rawconfig { - my ($class, $plugin_config, $config) = @_; - return ""; -} - -sub write_controller_config { - my ($class, $plugin_config, $config) = @_; - return; -} - -sub reload_controller { - my ($class) = @_; - return; -} - -1; - - diff --git a/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/PVE/Network/SDN/Controllers/EvpnPlugin.pm deleted file mode 100644 index 727aeaa..0000000 --- a/PVE/Network/SDN/Controllers/EvpnPlugin.pm +++ /dev/null @@ -1,542 +0,0 @@ -package PVE::Network::SDN::Controllers::EvpnPlugin; - -use strict; -use warnings; - -use PVE::INotify; -use PVE::JSONSchema qw(get_standard_option); -use PVE::Tools qw(run_command file_set_contents file_get_contents); - -use PVE::Network::SDN::Controllers::Plugin; -use PVE::Network::SDN::Zones::Plugin; -use Net::IP; - -use base('PVE::Network::SDN::Controllers::Plugin'); - -sub type { - return 'evpn'; -} - -sub properties { - return { - asn => { - type => 'integer', - description => "autonomous system number", - minimum => 0, - maximum => 4294967296 - }, - peers => { - description => "peers address list.", - type => 'string', format => 'ip-list' - }, - }; -} - -sub options { - return { - 'asn' => { optional => 0 }, - 'peers' => { optional => 0 }, - }; -} - -# Plugin implementation -sub generate_controller_config { - my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; - - my @peers; - @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; - - my $local_node = PVE::INotify::nodename(); - - my $asn = $plugin_config->{asn}; - my $ebgp = undef; - my $loopback = undef; - my $autortas = undef; - my $bgprouter = find_bgp_controller($local_node, $controller_cfg); - if ($bgprouter) { - $ebgp = 1 if $plugin_config->{'asn'} ne $bgprouter->{asn}; - $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; - $asn = $bgprouter->{asn} if $bgprouter->{asn}; - $autortas = $plugin_config->{'asn'} if $ebgp; - } - - return if !$asn; - - my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {}; - - my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); - - my $remoteas = $ebgp ? "external" : $asn; - - #global options - my @controller_config = ( - "bgp router-id $ifaceip", - "no bgp default ipv4-unicast", - "coalesce-time 1000", - ); - - push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0; - - @controller_config = (); - - #VTEP neighbors - push @controller_config, "neighbor VTEP peer-group"; - push @controller_config, "neighbor VTEP remote-as $remoteas"; - push @controller_config, "neighbor VTEP bfd"; - - if($ebgp && $loopback) { - push @controller_config, "neighbor VTEP ebgp-multihop 10"; - push @controller_config, "neighbor VTEP update-source $loopback"; - } - - # VTEP peers - foreach my $address (@peers) { - next if $address eq $ifaceip; - push @controller_config, "neighbor $address peer-group VTEP"; - } - - push(@{$bgp->{""}}, @controller_config); - - # address-family l2vpn - @controller_config = (); - push @controller_config, "neighbor VTEP route-map MAP_VTEP_IN in"; - push @controller_config, "neighbor VTEP route-map MAP_VTEP_OUT out"; - push @controller_config, "neighbor VTEP activate"; - push @controller_config, "advertise-all-vni"; - push @controller_config, "autort as $autortas" if $autortas; - push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config); - - my $routemap = { rule => undef, action => "permit" }; - push(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap ); - push(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap ); - - return $config; -} - -sub generate_controller_zone_config { - my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; - - my $local_node = PVE::INotify::nodename(); - - my $vrf = "vrf_$id"; - my $vrfvxlan = $plugin_config->{'vrf-vxlan'}; - my $exitnodes = $plugin_config->{'exitnodes'}; - my $exitnodes_primary = $plugin_config->{'exitnodes-primary'}; - my $advertisesubnets = $plugin_config->{'advertise-subnets'}; - my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'}; - my $rt_import; - $rt_import = [PVE::Tools::split_list($plugin_config->{'rt-import'})] if $plugin_config->{'rt-import'}; - - my $asn = $controller->{asn}; - my @peers; - @peers = PVE::Tools::split_list($controller->{'peers'}) if $controller->{'peers'}; - my $ebgp = undef; - my $loopback = undef; - my $autortas = undef; - my $bgprouter = find_bgp_controller($local_node, $controller_cfg); - if($bgprouter) { - $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn}; - $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; - $asn = $bgprouter->{asn} if $bgprouter->{asn}; - $autortas = $controller->{'asn'} if $ebgp; - } - - return if !$vrf || !$vrfvxlan || !$asn; - - my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); - - # vrf - my @controller_config = (); - push @controller_config, "vni $vrfvxlan"; - push(@{$config->{frr}->{vrf}->{"$vrf"}}, @controller_config); - - #main vrf router - @controller_config = (); - push @controller_config, "bgp router-id $ifaceip"; -# push @controller_config, "!"; - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config); - - if ($autortas) { - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target import $autortas:$vrfvxlan"); - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target export $autortas:$vrfvxlan"); - } - - my $is_gateway = $exitnodes->{$local_node}; - - if ($is_gateway) { - - if (!$exitnodes_primary || $exitnodes_primary eq $local_node) { - #filter default type5 route coming from other exit nodes on primary node or both nodes if no primary is defined. - my $routemap_config = (); - push @{$routemap_config}, "match evpn route-type prefix"; - my $routemap = { rule => $routemap_config, action => "deny" }; - unshift(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap); - } elsif ($exitnodes_primary ne $local_node) { - my $routemap_config = (); - push @{$routemap_config}, "match evpn vni $vrfvxlan"; - push @{$routemap_config}, "match evpn route-type prefix"; - push @{$routemap_config}, "set metric 200"; - my $routemap = { rule => $routemap_config, action => "permit" }; - unshift(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap); - } - - - if (!$exitnodes_local_routing) { - @controller_config = (); - #import /32 routes of evpn network from vrf1 to default vrf (for packet return) - push @controller_config, "import vrf $vrf"; - push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config); - push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config); - - @controller_config = (); - #redistribute connected to be able to route to local vms on the gateway - push @controller_config, "redistribute connected"; - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config); - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config); - } - - @controller_config = (); - #add default originate to announce 0.0.0.0/0 type5 route in evpn - push @controller_config, "default-originate ipv4"; - push @controller_config, "default-originate ipv6"; - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config); - } elsif ($advertisesubnets) { - - @controller_config = (); - #redistribute connected networks - push @controller_config, "redistribute connected"; - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config); - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config); - - @controller_config = (); - #advertise connected networks type5 route in evpn - push @controller_config, "advertise ipv4 unicast"; - push @controller_config, "advertise ipv6 unicast"; - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config); - } - - if ($rt_import) { - @controller_config = (); - foreach my $rt (sort @{$rt_import}) { - push @controller_config, "route-target import $rt"; - } - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config); - } - - return $config; -} - -sub generate_controller_vnet_config { - my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_; - - my $exitnodes = $zone->{'exitnodes'}; - my $exitnodes_local_routing = $zone->{'exitnodes-local-routing'}; - - return if !$exitnodes_local_routing; - - my $local_node = PVE::INotify::nodename(); - my $is_gateway = $exitnodes->{$local_node}; - - return if !$is_gateway; - - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); - my @controller_config = (); - foreach my $subnetid (sort keys %{$subnets}) { - my $subnet = $subnets->{$subnetid}; - my $cidr = $subnet->{cidr}; - push @controller_config, "ip route $cidr 10.255.255.2 xvrf_$zoneid"; - } - push(@{$config->{frr}->{''}}, @controller_config); -} - -sub on_delete_hook { - my ($class, $controllerid, $zone_cfg) = @_; - - # verify that zone is associated to this controller - foreach my $id (keys %{$zone_cfg->{ids}}) { - my $zone = $zone_cfg->{ids}->{$id}; - die "controller $controllerid is used by $id" - if (defined($zone->{controller}) && $zone->{controller} eq $controllerid); - } -} - -sub on_update_hook { - my ($class, $controllerid, $controller_cfg) = @_; - - # we can only have 1 evpn controller / 1 asn by server - - my $controllernb = 0; - foreach my $id (keys %{$controller_cfg->{ids}}) { - next if $id eq $controllerid; - my $controller = $controller_cfg->{ids}->{$id}; - next if $controller->{type} ne "evpn"; - $controllernb++; - die "only 1 global evpn controller can be defined" if $controllernb >= 1; - } -} - -sub find_bgp_controller { - my ($nodename, $controller_cfg) = @_; - - my $controller = undef; - foreach my $id (keys %{$controller_cfg->{ids}}) { - $controller = $controller_cfg->{ids}->{$id}; - next if $controller->{type} ne 'bgp'; - next if $controller->{node} ne $nodename; - last; - } - - return $controller; -} - - -sub sort_frr_config { - my $order = {}; - $order->{''} = 0; - $order->{'vrf'} = 1; - $order->{'ipv4 unicast'} = 1; - $order->{'ipv6 unicast'} = 2; - $order->{'l2vpn evpn'} = 3; - - my $a_val = 100; - my $b_val = 100; - - $a_val = $order->{$a} if defined($order->{$a}); - $b_val = $order->{$b} if defined($order->{$b}); - - if ($a =~ /bgp (\d+)$/) { - $a_val = 2; - } - - if ($b =~ /bgp (\d+)$/) { - $b_val = 2; - } - - return $a_val <=> $b_val; -} - -sub generate_frr_recurse{ - my ($final_config, $content, $parentkey, $level) = @_; - - my $keylist = {}; - $keylist->{vrf} = 1; - $keylist->{'address-family'} = 1; - $keylist->{router} = 1; - - my $exitkeylist = {}; - $exitkeylist->{vrf} = 1; - $exitkeylist->{'address-family'} = 1; - - my $simple_exitkeylist = {}; - $simple_exitkeylist->{router} = 1; - - # FIXME: make this generic - my $paddinglevel = undef; - if ($level == 1 || $level == 2) { - $paddinglevel = $level - 1; - } elsif ($level == 3 || $level == 4) { - $paddinglevel = $level - 2; - } - - my $padding = ""; - $padding = ' ' x ($paddinglevel) if $paddinglevel; - - if (ref $content eq 'HASH') { - foreach my $key (sort sort_frr_config keys %$content) { - if ($parentkey && defined($keylist->{$parentkey})) { - push @{$final_config}, $padding."!"; - push @{$final_config}, $padding."$parentkey $key"; - } elsif ($key ne '' && !defined($keylist->{$key})) { - push @{$final_config}, $padding."$key"; - } - - my $option = $content->{$key}; - generate_frr_recurse($final_config, $option, $key, $level+1); - - push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey}); - push @{$final_config}, $padding."exit" if $parentkey && defined($simple_exitkeylist->{$parentkey}); - } - } - - if (ref $content eq 'ARRAY') { - push @{$final_config}, map { $padding . "$_" } @$content; - } -} - -sub generate_frr_routemap { - my ($final_config, $routemaps) = @_; - - foreach my $id (sort keys %$routemaps) { - - my $routemap = $routemaps->{$id}; - my $order = 0; - foreach my $seq (@$routemap) { - $order++; - next if !defined($seq->{action}); - my @config = (); - push @config, "!"; - push @config, "route-map $id $seq->{action} $order"; - my $rule = $seq->{rule}; - push @config, map { " $_" } @$rule; - push @{$final_config}, @config; - push @{$final_config}, "exit"; - } - } -} - -sub generate_frr_list { - my ($final_config, $lists, $type) = @_; - - my $config = []; - - for my $id (sort keys %$lists) { - my $list = $lists->{$id}; - - for my $seq (sort keys %$list) { - my $rule = $list->{$seq}; - push @$config, "$type $id seq $seq $rule"; - } - } - - if (@$config > 0) { - push @{$final_config}, "!", @$config; - } -} - -sub generate_controller_rawconfig { - my ($class, $plugin_config, $config) = @_; - - my $nodename = PVE::INotify::nodename(); - - my $final_config = []; - push @{$final_config}, "frr version 8.2.2"; - push @{$final_config}, "frr defaults datacenter"; - push @{$final_config}, "hostname $nodename"; - push @{$final_config}, "log syslog informational"; - push @{$final_config}, "service integrated-vtysh-config"; - push @{$final_config}, "!"; - - if (-e "/etc/frr/frr.conf.local") { - my $local_conf = file_get_contents("/etc/frr/frr.conf.local"); - parse_merge_frr_local_config($config, $local_conf); - } - - generate_frr_recurse($final_config, $config->{frr}, undef, 0); - generate_frr_list($final_config, $config->{frr_access_list}, "access-list"); - generate_frr_list($final_config, $config->{frr_prefix_list}, "ip prefix-list"); - generate_frr_routemap($final_config, $config->{frr_routemap}); - - push @{$final_config}, "!"; - push @{$final_config}, "line vty"; - push @{$final_config}, "!"; - - my $rawconfig = join("\n", @{$final_config}); - - return if !$rawconfig; - return $rawconfig; -} - -sub parse_merge_frr_local_config { - my ($config, $local_conf) = @_; - - my $section = \$config->{""}; - my $router = undef; - my $routemap = undef; - my $routemap_config = (); - my $routemap_action = undef; - - while ($local_conf =~ /^\s*(.+?)\s*$/gm) { - my $line = $1; - $line =~ s/^\s+|\s+$//g; - - if ($line =~ m/^router (.+)$/) { - $router = $1; - $section = \$config->{'frr'}->{'router'}->{$router}->{""}; - next; - } elsif ($line =~ m/^vrf (.+)$/) { - $section = \$config->{'frr'}->{'vrf'}->{$1}; - next; - } elsif ($line =~ m/address-family (.+)$/) { - $section = \$config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1}; - next; - } elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) { - $routemap = $1; - $routemap_config = (); - $routemap_action = $2; - $section = \$config->{'frr_routemap'}->{$routemap}; - next; - } elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) { - $config->{'frr_access_list'}->{$1}->{$2} = $3; - next; - } elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) { - $config->{'frr_prefix_list'}->{$1}->{$2} = $3; - next; - } elsif($line =~ m/^exit-address-family$/) { - next; - } elsif($line =~ m/^exit$/) { - if($router) { - $section = \$config->{''}; - $router = undef; - } elsif($routemap) { - push(@{$$section}, { rule => $routemap_config, action => $routemap_action }); - $section = \$config->{''}; - $routemap = undef; - $routemap_action = undef; - $routemap_config = (); - } - next; - } elsif($line =~ m/!/) { - next; - } - - next if !$section; - if($routemap) { - push(@{$routemap_config}, $line); - } else { - push(@{$$section}, $line); - } - } -} - -sub write_controller_config { - my ($class, $plugin_config, $config) = @_; - - my $rawconfig = $class->generate_controller_rawconfig($plugin_config, $config); - return if !$rawconfig; - return if !-d "/etc/frr"; - - file_set_contents("/etc/frr/frr.conf", $rawconfig); -} - -sub reload_controller { - my ($class) = @_; - - my $conf_file = "/etc/frr/frr.conf"; - my $bin_path = "/usr/lib/frr/frr-reload.py"; - - if (!-e $bin_path) { - warn "missing $bin_path. Please install frr-pythontools package"; - return; - } - - my $err = sub { - my $line = shift; - if ($line =~ /ERROR:/) { - warn "$line \n"; - } - }; - - if (-e $conf_file && -e $bin_path) { - eval { - run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err); - }; - if ($@) { - warn "frr reload command fail. Restarting frr."; - eval { run_command(['systemctl', 'restart', 'frr']); }; - } - } -} - -1; - - diff --git a/PVE/Network/SDN/Controllers/FaucetPlugin.pm b/PVE/Network/SDN/Controllers/FaucetPlugin.pm deleted file mode 100644 index 4f3bb5c..0000000 --- a/PVE/Network/SDN/Controllers/FaucetPlugin.pm +++ /dev/null @@ -1,97 +0,0 @@ -package PVE::Network::SDN::Controllers::FaucetPlugin; - -use strict; -use warnings; -use PVE::Network::SDN::Controllers::Plugin; -use PVE::Tools; -use PVE::INotify; -use PVE::JSONSchema qw(get_standard_option); -use CPAN::Meta::YAML; -use Encode; - -use base('PVE::Network::SDN::Controllers::Plugin'); - -sub type { - return 'faucet'; -} - -sub properties { - return { - }; -} - -# Plugin implementation -sub generate_controller_config { - my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; - -} - -sub generate_controller_zone_config { - my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; - - my $dpid = $plugin_config->{'dp-id'}; - my $dphex = printf("%x",$dpid); - - my $zone_config = { - dp_id => $dphex, - hardware => "Open vSwitch", - }; - - $config->{faucet}->{dps}->{$id} = $zone_config; - -} - - -sub generate_controller_vnet_config { - my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_; - - my $mac = $plugin_config->{mac}; - my $ipv4 = $plugin_config->{ipv4}; - my $ipv6 = $plugin_config->{ipv6}; - my $tag = $plugin_config->{tag}; - my $alias = $plugin_config->{alias}; - - my @ips = (); - push @ips, $ipv4 if $ipv4; - push @ips, $ipv6 if $ipv6; - - my $vlan_config = { vid => $tag }; - - $vlan_config->{description} = $alias if $alias; - $vlan_config->{faucet_mac} = $mac if $mac; - $vlan_config->{faucet_vips} = \@ips if scalar @ips > 0; - - $config->{faucet}->{vlans}->{$vnetid} = $vlan_config; - - push(@{$config->{faucet}->{routers}->{$zoneid}->{vlans}} , $vnetid); - -} - -sub write_controller_config { - my ($class, $plugin_config, $config) = @_; - - my $rawconfig = encode('UTF-8', CPAN::Meta::YAML::Dump($config->{faucet})); - - return if !$rawconfig; - return if !-d "/etc/faucet"; - - my $frr_config_file = "/etc/faucet/faucet.yaml"; - - my $writefh = IO::File->new($frr_config_file,">"); - print $writefh $rawconfig; - $writefh->close(); -} - -sub reload_controller { - my ($class) = @_; - - my $conf_file = "/etc/faucet/faucet.yaml"; - my $bin_path = "/usr/bin/faucet"; - - if (-e $conf_file && -e $bin_path) { - PVE::Tools::run_command(['systemctl', 'reload', 'faucet']); - } -} - -1; - diff --git a/PVE/Network/SDN/Controllers/Makefile b/PVE/Network/SDN/Controllers/Makefile deleted file mode 100644 index 11686a3..0000000 --- a/PVE/Network/SDN/Controllers/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Controllers/$$i; done diff --git a/PVE/Network/SDN/Controllers/Plugin.pm b/PVE/Network/SDN/Controllers/Plugin.pm deleted file mode 100644 index c1c2cfd..0000000 --- a/PVE/Network/SDN/Controllers/Plugin.pm +++ /dev/null @@ -1,121 +0,0 @@ -package PVE::Network::SDN::Controllers::Plugin; - -use strict; -use warnings; - -use PVE::Tools; -use PVE::JSONSchema; -use PVE::Cluster; - -use Data::Dumper; -use PVE::JSONSchema qw(get_standard_option); -use base qw(PVE::SectionConfig); - -PVE::Cluster::cfs_register_file('sdn/controllers.cfg', - sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_config(@_); } -); - -PVE::JSONSchema::register_standard_option('pve-sdn-controller-id', { - description => "The SDN controller object identifier.", - type => 'string', format => 'pve-sdn-controller-id', -}); - -PVE::JSONSchema::register_format('pve-sdn-controller-id', \&parse_sdn_controller_id); -sub parse_sdn_controller_id { - my ($id, $noerr) = @_; - - if ($id !~ m/^[a-z][a-z0-9_-]*[a-z0-9]$/i) { - return undef if $noerr; - die "controller ID '$id' contains illegal characters\n"; - } - die "controller ID '$id' can't be more length than 64 characters\n" if length($id) > 64; - return $id; -} - -my $defaultData = { - - propertyList => { - type => { - description => "Plugin type.", - type => 'string', format => 'pve-configid', - type => 'string', - }, - controller => get_standard_option('pve-sdn-controller-id', - { completion => \&PVE::Network::SDN::complete_sdn_controller }), - }, -}; - -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 generate_sdn_config { - my ($class, $plugin_config, $node, $data, $ctime) = @_; - - die "please implement inside plugin"; -} - -sub generate_controller_config { - my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; - - die "please implement inside plugin"; -} - - -sub generate_controller_zone_config { - my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; - - die "please implement inside plugin"; -} - -sub generate_controller_vnet_config { - my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_; - -} - -sub generate_controller_rawconfig { - my ($class, $plugin_config, $config) = @_; - - die "please implement inside plugin"; -} - -sub write_controller_config { - my ($class, $plugin_config, $config) = @_; - - die "please implement inside plugin"; -} - -sub controller_reload { - my ($class) = @_; - - die "please implement inside plugin"; -} - -sub on_delete_hook { - my ($class, $controllerid, $zone_cfg) = @_; - - # do nothing by default -} - -sub on_update_hook { - my ($class, $controllerid, $controller_cfg) = @_; - - # do nothing by default -} - -1; diff --git a/PVE/Network/SDN/Dns.pm b/PVE/Network/SDN/Dns.pm deleted file mode 100644 index c2e153a..0000000 --- a/PVE/Network/SDN/Dns.pm +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index 81cd2a1..0000000 --- a/PVE/Network/SDN/Dns/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 07d0be1..0000000 --- a/PVE/Network/SDN/Dns/Plugin.pm +++ /dev/null @@ -1,109 +0,0 @@ -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 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 }, - reversev6mask => { 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, $zone, $hostname, $ip, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub add_ptr_record { - my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub del_ptr_record { - my ($class, $plugin_config, $zone, $ip, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub del_a_record { - my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub verify_zone { - my ($class, $plugin_config, $zone, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub get_reversedns_zone { - my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; - - die "please implement inside plugin"; -} - -sub on_update_hook { - my ($class, $plugin_config) = @_; -} - -1; diff --git a/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/PVE/Network/SDN/Dns/PowerdnsPlugin.pm deleted file mode 100644 index 096d131..0000000 --- a/PVE/Network/SDN/Dns/PowerdnsPlugin.pm +++ /dev/null @@ -1,329 +0,0 @@ -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 NetAddr::IP qw(:lower); -use base('PVE::Network::SDN::Dns::Plugin'); - -sub type { - return 'powerdns'; -} - -sub properties { - return { - url => { - type => 'string', - }, - key => { - type => 'string', - }, - reversemaskv6 => { - type => 'integer' - }, - }; -} - -sub options { - - return { - url => { optional => 0}, - key => { optional => 0 }, - ttl => { optional => 1 }, - reversemaskv6 => { optional => 1, description => "force a different netmask for the ipv6 reverse zone name." }, - - }; -} - -# Plugin implementation - -sub add_a_record { - my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; - - 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 $zonecontent = get_zone_content($plugin_config, $zone); - my $existing_rrset = get_zone_rrset($zonecontent, $fqdn); - - my $final_records = []; - my $foundrecord = undef; - foreach my $record (@{$existing_rrset->{records}}) { - if($record->{content} eq $ip) { - $foundrecord = 1; - next; - } - push @$final_records, $record; - } - return if $foundrecord; - - my $record = { content => $ip, - disabled => JSON::false, - name => $fqdn, - type => $type, - priority => 0 }; - - push @$final_records, $record; - - my $rrset = { name => $fqdn, - type => $type, - ttl => $ttl, - changetype => "REPLACE", - records => $final_records }; - - - my $params = { rrsets => [ $rrset ] }; - - eval { - PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params); - }; - - if ($@) { - die "error add $fqdn to zone $zone: $@" if !$noerr; - } -} - -sub add_ptr_record { - my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; - - 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]; - $hostname .= "."; - - my $reverseip = Net::IP->new($ip)->reverse_ip(); - - my $type = "PTR"; - - my $record = { content => $hostname, - 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::api_request("PATCH", "$url/zones/$zone", $headers, $params); - }; - - if ($@) { - die "error add $reverseip to zone $zone: $@" if !$noerr; - } -} - -sub del_a_record { - my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; - - 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 = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A"; - - my $zonecontent = get_zone_content($plugin_config, $zone); - my $existing_rrset = get_zone_rrset($zonecontent, $fqdn); - - my $final_records = []; - my $foundrecord = undef; - foreach my $record (@{$existing_rrset->{records}}) { - if ($record->{content} eq $ip) { - $foundrecord = 1; - next; - } - push @$final_records, $record; - } - return if !$foundrecord; - - my $rrset = {}; - - if (scalar (@{$final_records}) > 0) { - #if we still have other records, we rewrite them without removed ip - $rrset = { name => $fqdn, - type => $type, - ttl => $existing_rrset->{ttl}, - changetype => "REPLACE", - records => $final_records }; - - } else { - - $rrset = { name => $fqdn, - type => $type, - changetype => "DELETE", - records => [] }; - } - - my $params = { rrsets => [ $rrset ] }; - - eval { - PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params); - }; - - if ($@) { - die "error delete $fqdn from zone $zone: $@" if !$noerr; - } -} - -sub del_ptr_record { - my ($class, $plugin_config, $zone, $ip, $noerr) = @_; - - 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 = Net::IP->new($ip)->reverse_ip(); - - my $type = "PTR"; - - my $rrset = { name => $reverseip, - type => $type, - changetype => "DELETE", - records => [] }; - - my $params = { rrsets => [ $rrset ] }; - - eval { - PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params); - }; - - if ($@) { - die "error delete $reverseip from zone $zone: $@" if !$noerr; - } -} - -sub verify_zone { - my ($class, $plugin_config, $zone, $noerr) = @_; - - #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::api_request("GET", "$url/zones/$zone?rrsets=false", $headers); - }; - - if ($@) { - die "can't read zone $zone: $@" if !$noerr; - } -} - -sub get_reversedns_zone { - my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; - - my $cidr = $subnet->{cidr}; - my $mask = $subnet->{mask}; - - my $zone = ""; - - if (Net::IP::ip_is_ipv4($ip)) { - my ($ipblock1, $ipblock2, $ipblock3, $ipblock4) = split(/\./, $ip); - - my $ipv4 = NetAddr::IP->new($cidr); - #private addresse #powerdns built-in private zone : serve-rfc1918 - if($ipv4->is_rfc1918()) { - if ($ipblock1 == 192) { - $zone = "168.192.in-addr.arpa."; - } elsif ($ipblock1 == 172) { - $zone = "16-31.172.in-addr.arpa."; - } elsif ($ipblock1 == 10) { - $zone = "10.in-addr.arpa."; - } - - } else { - #public ipv4 : RIPE,ARIN,AFRNIC - #. Delegations can be managed in IPv4 on bit boundaries (/8, /16 or /24s), and IPv6 networks can be managed on nibble boundaries (every 4 bits of the IPv6 address) - #One or more /24 type zones need to be created if your address space has a prefix length between /17 and /24. - # If your prefix length is between /16 and /9 you will have to request one or more delegations for /16 type zones. - - if ($mask <= 24) { - $zone = "$ipblock3.$ipblock2.$ipblock1.in-addr.arpa."; - } elsif ($mask <= 16) { - $zone = "$ipblock2.$ipblock1.in-addr.arpa."; - } elsif ($mask <= 8) { - $zone = "$ipblock1.in-addr.arpa."; - } - } - } else { - $mask = $plugin_config->{reversemaskv6} if $plugin_config->{reversemaskv6}; - die "reverse dns zone mask need to be a multiple of 4" if ($mask % 4); - my $networkv6 = NetAddr::IP->new($cidr)->network(); - $zone = Net::IP->new($networkv6)->reverse_ip(); - } - - return $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::api_request("GET", "$url", $headers); - }; - - if ($@) { - die "dns api error: $@"; - } -} - - -sub get_zone_content { - my ($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]; - - my $result = undef; - eval { - $result = PVE::Network::SDN::api_request("GET", "$url/zones/$zone", $headers); - }; - - if ($@) { - die "can't read zone $zone: $@"; - } - return $result; -} - -sub get_zone_rrset { - my ($zonecontent, $name) = @_; - - my $rrsetresult = undef; - foreach my $rrset (@{$zonecontent->{rrsets}}) { - next if $rrset->{name} ne $name; - $rrsetresult = $rrset; - last; - } - return $rrsetresult; -} - -1; - - diff --git a/PVE/Network/SDN/Ipams.pm b/PVE/Network/SDN/Ipams.pm deleted file mode 100644 index e8a4b0b..0000000 --- a/PVE/Network/SDN/Ipams.pm +++ /dev/null @@ -1,69 +0,0 @@ -package PVE::Network::SDN::Ipams; - -use strict; -use warnings; - -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::Ipams::PVEPlugin; -use PVE::Network::SDN::Ipams::NetboxPlugin; -use PVE::Network::SDN::Ipams::PhpIpamPlugin; -use PVE::Network::SDN::Ipams::Plugin; - -PVE::Network::SDN::Ipams::PVEPlugin->register(); -PVE::Network::SDN::Ipams::NetboxPlugin->register(); -PVE::Network::SDN::Ipams::PhpIpamPlugin->register(); -PVE::Network::SDN::Ipams::Plugin->init(); - - -sub sdn_ipams_config { - my ($cfg, $id, $noerr) = @_; - - die "no sdn ipam 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/ipams.cfg"); - #add default internal pve - $config->{ids}->{pve}->{type} = 'pve'; - return $config; -} - -sub get_plugin_config { - my ($vnet) = @_; - my $ipamid = $vnet->{ipam}; - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - return $ipam_cfg->{ids}->{$ipamid}; -} - -sub write_config { - my ($cfg) = @_; - - cfs_write_file("sdn/ipams.cfg", $cfg); -} - -sub sdn_ipams_ids { - my ($cfg) = @_; - - return keys %{$cfg->{ids}}; -} - -sub complete_sdn_vnet { - my ($cmdname, $pname, $cvalue) = @_; - - my $cfg = PVE::Network::SDN::Ipams::config(); - - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ]; -} - -1; - diff --git a/PVE/Network/SDN/Ipams/Makefile b/PVE/Network/SDN/Ipams/Makefile deleted file mode 100644 index 4e7d65f..0000000 --- a/PVE/Network/SDN/Ipams/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Ipams/$$i; done diff --git a/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/PVE/Network/SDN/Ipams/NetboxPlugin.pm deleted file mode 100644 index f0e7168..0000000 --- a/PVE/Network/SDN/Ipams/NetboxPlugin.pm +++ /dev/null @@ -1,226 +0,0 @@ -package PVE::Network::SDN::Ipams::NetboxPlugin; - -use strict; -use warnings; -use PVE::INotify; -use PVE::Cluster; -use PVE::Tools; - -use base('PVE::Network::SDN::Ipams::Plugin'); - -sub type { - return 'netbox'; -} - -sub properties { - return { - }; -} - -sub options { - - return { - url => { optional => 0}, - token => { optional => 0 }, - }; -} - -# Plugin implementation - -sub add_subnet { - my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $gateway = $subnet->{gateway}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - - my $internalid = get_prefix_id($url, $cidr, $headers); - - #create subnet - if (!$internalid) { - - my $params = { prefix => $cidr }; - - eval { - my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/", $headers, $params); - }; - if ($@) { - die "error add subnet to ipam: $@" if !$noerr; - } - } - -} - -sub del_subnet { - my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $gateway = $subnet->{gateway}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - - my $internalid = get_prefix_id($url, $cidr, $headers); - return if !$internalid; - - return; #fixme: check that prefix is empty exluding gateway, before delete - - eval { - PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers); - }; - if ($@) { - die "error deleting subnet from ipam: $@" if !$noerr; - } - -} - -sub add_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; - - my $mask = $subnet->{mask}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - $description .= " mac:$mac" if $mac && $description; - - my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description }; - - eval { - PVE::Network::SDN::api_request("POST", "$url/ipam/ip-addresses/", $headers, $params); - }; - - if ($@) { - if($is_gateway) { - die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr; - } else { - die "error add subnet ip to ipam: ip already exist: $@" if !$noerr; - } - } -} - -sub update_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; - - my $mask = $subnet->{mask}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - $description .= " mac:$mac" if $mac && $description; - - my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description }; - - my $ip_id = get_ip_id($url, $ip, $headers); - die "can't find ip $ip in ipam" if !$ip_id; - - eval { - PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params); - }; - if ($@) { - die "error update ip $ip : $@" if !$noerr; - } -} - -sub add_next_freeip { - my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - - my $internalid = get_prefix_id($url, $cidr, $headers); - $description .= " mac:$mac" if $mac && $description; - - my $params = { dns_name => $hostname, description => $description }; - - my $ip = undef; - eval { - my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params); - $ip = $result->{address}; - }; - - if ($@) { - die "can't find free ip in subnet $cidr: $@" if !$noerr; - } - - return $ip; -} - -sub del_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_; - - return if !$ip; - - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - - my $ip_id = get_ip_id($url, $ip, $headers); - die "can't find ip $ip in ipam" if !$ip_id; - - eval { - PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers); - }; - if ($@) { - die "error delete ip $ip : $@" if !$noerr; - } -} - -sub verify_api { - my ($class, $plugin_config) = @_; - - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; - - - eval { - PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers); - }; - if ($@) { - die "Can't connect to netbox api: $@"; - } -} - -sub on_update_hook { - my ($class, $plugin_config) = @_; - - PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config); -} - -#helpers - -sub get_prefix_id { - my ($url, $cidr, $headers) = @_; - - my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers); - my $data = @{$result->{results}}[0]; - my $internalid = $data->{id}; - return $internalid; -} - -sub get_ip_id { - my ($url, $ip, $headers) = @_; - my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers); - my $data = @{$result->{results}}[0]; - my $ip_id = $data->{id}; - return $ip_id; -} - -sub is_ip_gateway { - my ($url, $ip, $headers) = @_; - my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers); - my $data = @{$result->{data}}[0]; - my $description = $data->{description}; - my $is_gateway = 1 if $description eq 'gateway'; - return $is_gateway; -} - -1; - - diff --git a/PVE/Network/SDN/Ipams/PVEPlugin.pm b/PVE/Network/SDN/Ipams/PVEPlugin.pm deleted file mode 100644 index 3e8ffc5..0000000 --- a/PVE/Network/SDN/Ipams/PVEPlugin.pm +++ /dev/null @@ -1,210 +0,0 @@ -package PVE::Network::SDN::Ipams::PVEPlugin; - -use strict; -use warnings; -use PVE::INotify; -use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file cfs_lock_file); -use PVE::Tools; -use JSON; -use NetAddr::IP qw(:lower); - -use Net::IP; -use Digest::SHA; - -use base('PVE::Network::SDN::Ipams::Plugin'); - - -my $ipamdb_file = "priv/ipam.db"; - -PVE::Cluster::cfs_register_file($ipamdb_file, - sub { PVE::Network::SDN::Ipams::PVEPlugin->parse_config(@_); }, - sub { PVE::Network::SDN::Ipams::PVEPlugin->write_config(@_); }); - -sub type { - return 'pve'; -} - -sub properties { -} - -sub options { -} - -# Plugin implementation - -sub add_subnet { - my ($class, $plugin_config, $subnetid, $subnet) = @_; - - my $cidr = $subnet->{cidr}; - my $zone = $subnet->{zone}; - my $gateway = $subnet->{gateway}; - - - cfs_lock_file($ipamdb_file, undef, sub { - my $db = {}; - $db = read_db(); - - $db->{zones}->{$zone} = {} if !$db->{zones}->{$zone}; - my $zonedb = $db->{zones}->{$zone}; - - if(!$zonedb->{subnets}->{$cidr}) { - #create subnet - $zonedb->{subnets}->{$cidr}->{ips} = {}; - write_db($db); - } - }); - die "$@" if $@; -} - -sub del_subnet { - my ($class, $plugin_config, $subnetid, $subnet) = @_; - - my $cidr = $subnet->{cidr}; - my $zone = $subnet->{zone}; - - cfs_lock_file($ipamdb_file, undef, sub { - - my $db = read_db(); - - my $dbzone = $db->{zones}->{$zone}; - die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; - my $dbsubnet = $dbzone->{subnets}->{$cidr}; - die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; - - die "cannot delete subnet '$cidr', not empty\n" if keys %{$dbsubnet->{ips}} > 0; - - delete $dbzone->{subnets}->{$cidr}; - - write_db($db); - }); - die "$@" if $@; - -} - -sub add_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_; - - my $cidr = $subnet->{cidr}; - my $zone = $subnet->{zone}; - - cfs_lock_file($ipamdb_file, undef, sub { - - my $db = read_db(); - my $dbzone = $db->{zones}->{$zone}; - die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; - my $dbsubnet = $dbzone->{subnets}->{$cidr}; - die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; - - die "IP '$ip' already exist\n" if (!$is_gateway && defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && defined($dbsubnet->{ips}->{$ip}) && !defined($dbsubnet->{ips}->{$ip}->{gateway})); - $dbsubnet->{ips}->{$ip} = {}; - $dbsubnet->{ips}->{$ip} = {gateway => 1} if $is_gateway; - - write_db($db); - }); - die "$@" if $@; -} - -sub update_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_; - return; -} - -sub add_next_freeip { - my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description) = @_; - - my $cidr = $subnet->{cidr}; - my $network = $subnet->{network}; - my $zone = $subnet->{zone}; - my $mask = $subnet->{mask}; - my $freeip = undef; - - cfs_lock_file($ipamdb_file, undef, sub { - - my $db = read_db(); - my $dbzone = $db->{zones}->{$zone}; - die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; - my $dbsubnet = $dbzone->{subnets}->{$cidr}; - die "subnet '$cidr' doesn't exist in IPAM DB" if !$dbsubnet; - - if (Net::IP::ip_is_ipv4($network) && $mask == 32) { - die "cannot find free IP in subnet '$cidr'\n" if defined($dbsubnet->{ips}->{$network}); - $freeip = $network; - } else { - my $iplist = NetAddr::IP->new($cidr); - my $lastip = $iplist->last()->canon(); - $iplist++ if Net::IP::ip_is_ipv4($network); #skip network address for ipv4 - while(1) { - my $ip = $iplist->canon(); - if (defined($dbsubnet->{ips}->{$ip})) { - last if $ip eq $lastip; - $iplist++; - next; - } - $freeip = $ip; - last; - } - } - - die "can't find free ip in subnet '$cidr'\n" if !$freeip; - - $dbsubnet->{ips}->{$freeip} = {}; - - write_db($db); - }); - die "$@" if $@; - - return "$freeip/$mask"; -} - -sub del_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; - - my $cidr = $subnet->{cidr}; - my $zone = $subnet->{zone}; - - cfs_lock_file($ipamdb_file, undef, sub { - - my $db = read_db(); - die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone}; - my $dbzone = $db->{zones}->{$zone}; - die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr}; - my $dbsubnet = $dbzone->{subnets}->{$cidr}; - - die "IP '$ip' does not exist in IPAM DB\n" if !defined($dbsubnet->{ips}->{$ip}); - delete $dbsubnet->{ips}->{$ip}; - - write_db($db); - }); - die "$@" if $@; -} - -#helpers - -sub read_db { - my $db = cfs_read_file($ipamdb_file); - return $db; -} - -sub write_db { - my ($cfg) = @_; - - my $json = to_json($cfg); - cfs_write_file($ipamdb_file, $json); -} - -sub write_config { - my ($class, $filename, $cfg) = @_; - - return $cfg; -} - -sub parse_config { - my ($class, $filename, $raw) = @_; - - $raw = '{}' if !defined($raw) ||$raw eq ''; - my $cfg = from_json($raw); - - return $cfg; -} - -1; diff --git a/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm b/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm deleted file mode 100644 index ad5286b..0000000 --- a/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm +++ /dev/null @@ -1,259 +0,0 @@ -package PVE::Network::SDN::Ipams::PhpIpamPlugin; - -use strict; -use warnings; -use PVE::INotify; -use PVE::Cluster; -use PVE::Tools; - -use base('PVE::Network::SDN::Ipams::Plugin'); - -sub type { - return 'phpipam'; -} - -sub properties { - return { - url => { - type => 'string', - }, - token => { - type => 'string', - }, - section => { - type => 'integer', - }, - }; -} - -sub options { - - return { - url => { optional => 0}, - token => { optional => 0 }, - section => { optional => 0 }, - }; -} - -# Plugin implementation - -sub add_subnet { - my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $network = $subnet->{network}; - my $mask = $subnet->{mask}; - - my $gateway = $subnet->{gateway}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - #search subnet - my $internalid = get_prefix_id($url, $cidr, $headers); - - #create subnet - if (!$internalid) { - my $params = { subnet => $network, - mask => $mask, - sectionId => $section, - }; - - eval { - PVE::Network::SDN::api_request("POST", "$url/subnets/", $headers, $params); - }; - if ($@) { - die "error add subnet to ipam: $@" if !$noerr; - } - } - -} - -sub del_subnet { - my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - my $internalid = get_prefix_id($url, $cidr, $headers); - return if !$internalid; - - return; #fixme: check that prefix is empty exluding gateway, before delete - - eval { - PVE::Network::SDN::api_request("DELETE", "$url/subnets/$internalid", $headers); - }; - if ($@) { - die "error deleting subnet from ipam: $@" if !$noerr; - } - -} - -sub add_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - my $internalid = get_prefix_id($url, $cidr, $headers); - - my $params = { ip => $ip, - subnetId => $internalid, - hostname => $hostname, - description => $description, - }; - $params->{is_gateway} = 1 if $is_gateway; - $params->{mac} = $mac if $mac; - - eval { - PVE::Network::SDN::api_request("POST", "$url/addresses/", $headers, $params); - }; - - if ($@) { - if($is_gateway) { - die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr; - } else { - die "error add subnet ip to ipam: ip $ip already exist: $@" if !$noerr; - } - } -} - -sub update_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - my $ip_id = get_ip_id($url, $ip, $headers); - die "can't find ip addresse in ipam" if !$ip_id; - - my $params = { - hostname => $hostname, - description => $description, - }; - $params->{is_gateway} = 1 if $is_gateway; - $params->{mac} = $mac if $mac; - - eval { - PVE::Network::SDN::api_request("PATCH", "$url/addresses/$ip_id", $headers, $params); - }; - - if ($@) { - die "ipam: error update subnet ip $ip: $@" if !$noerr; - } -} - -sub add_next_freeip { - my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_; - - my $cidr = $subnet->{cidr}; - my $mask = $subnet->{mask}; - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $section = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - my $internalid = get_prefix_id($url, $cidr, $headers); - - my $params = { hostname => $hostname, - description => $description, - }; - - $params->{mac} = $mac if $mac; - - my $ip = undef; - eval { - my $result = PVE::Network::SDN::api_request("POST", "$url/addresses/first_free/$internalid/", $headers, $params); - $ip = $result->{data}; - }; - - if ($@) { - die "can't find free ip in subnet $cidr: $@" if !$noerr; - } - - return "$ip/$mask" if $ip && $mask; -} - -sub del_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_; - - return if !$ip; - - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - my $ip_id = get_ip_id($url, $ip, $headers); - return if !$ip_id; - - eval { - PVE::Network::SDN::api_request("DELETE", "$url/addresses/$ip_id", $headers); - }; - if ($@) { - die "error delete ip $ip: $@" if !$noerr; - } -} - -sub verify_api { - my ($class, $plugin_config) = @_; - - my $url = $plugin_config->{url}; - my $token = $plugin_config->{token}; - my $sectionid = $plugin_config->{section}; - my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; - - eval { - PVE::Network::SDN::api_request("GET", "$url/sections/$sectionid", $headers); - }; - if ($@) { - die "Can't connect to phpipam api: $@"; - } -} - -sub on_update_hook { - my ($class, $plugin_config) = @_; - - PVE::Network::SDN::Ipams::PhpIpamPlugin::verify_api($class, $plugin_config); -} - - -#helpers - -sub get_prefix_id { - my ($url, $cidr, $headers) = @_; - - my $result = PVE::Network::SDN::api_request("GET", "$url/subnets/cidr/$cidr", $headers); - my $data = @{$result->{data}}[0]; - my $internalid = $data->{id}; - return $internalid; -} - -sub get_ip_id { - my ($url, $ip, $headers) = @_; - my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers); - my $data = @{$result->{data}}[0]; - my $ip_id = $data->{id}; - return $ip_id; -} - -sub is_ip_gateway { - my ($url, $ip, $headers) = @_; - my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers); - my $data = @{$result->{data}}[0]; - my $is_gateway = $data->{is_gateway}; - return $is_gateway; -} - -1; - - diff --git a/PVE/Network/SDN/Ipams/Plugin.pm b/PVE/Network/SDN/Ipams/Plugin.pm deleted file mode 100644 index c96eeda..0000000 --- a/PVE/Network/SDN/Ipams/Plugin.pm +++ /dev/null @@ -1,111 +0,0 @@ -package PVE::Network::SDN::Ipams::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/ipams.cfg', - sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_config(@_); }); - -PVE::JSONSchema::register_standard_option('pve-sdn-ipam-id', { - description => "The SDN ipam object identifier.", - type => 'string', format => 'pve-sdn-ipam-id', -}); - -PVE::JSONSchema::register_format('pve-sdn-ipam-id', \&parse_sdn_ipam_id); -sub parse_sdn_ipam_id { - my ($id, $noerr) = @_; - - if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { - return undef if $noerr; - die "ipam ID '$id' contains illegal characters\n"; - } - return $id; -} - -my $defaultData = { - - propertyList => { - type => { - description => "Plugin type.", - type => 'string', format => 'pve-configid', - type => 'string', - }, - ipam => get_standard_option('pve-sdn-ipam-id', - { completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipam }), - }, -}; - -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_subnet { - my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub del_subnet { - my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub add_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub update_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; - # only update ip attributes (mac,hostname,..). Don't change the ip addresses itself, as some ipam - # don't allow ip address change without del/add - - die "please implement inside plugin"; -} - -sub add_next_freeip { - my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub del_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_; - - die "please implement inside plugin"; -} - -sub on_update_hook { - my ($class, $plugin_config) = @_; -} - -1; diff --git a/PVE/Network/SDN/Makefile b/PVE/Network/SDN/Makefile deleted file mode 100644 index 92cfcd0..0000000 --- a/PVE/Network/SDN/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/$$i; done - 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 deleted file mode 100644 index 15b370f..0000000 --- a/PVE/Network/SDN/SubnetPlugin.pm +++ /dev/null @@ -1,166 +0,0 @@ -package PVE::Network::SDN::SubnetPlugin; - -use strict; -use warnings; - -use Net::IP; -use Net::Subnet qw(subnet_matcher); - -use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); -use PVE::Exception qw(raise raise_param_exc); -use PVE::JSONSchema qw(get_standard_option); -use PVE::Network::SDN::Ipams; -use PVE::Network::SDN::Vnets; - -use base qw(PVE::SectionConfig); - -PVE::Cluster::cfs_register_file('sdn/subnets.cfg', - sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_config(@_); }); - -PVE::JSONSchema::register_standard_option('pve-sdn-subnet-id', { - description => "The SDN subnet object identifier.", - type => 'string', format => 'pve-sdn-subnet-id', - type => 'string' -}); - -PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id); -sub parse_sdn_subnet_id { - my ($id, $noerr) = @_; - - my $cidr = ""; - if($id =~ /\//) { - $cidr = $id; - } else { - my ($zone, $ip, $mask) = split(/-/, $id); - $cidr = "$ip/$mask"; - } - - if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) || - PVE::JSONSchema::pve_verify_cidrv6($cidr, 1))) - { - return undef if $noerr; - die "value does not look like a valid CIDR network\n"; - } - return $id; -} - -my $defaultData = { - - propertyList => { - subnet => get_standard_option('pve-sdn-subnet-id', - { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }), - }, -}; - -sub type { - return 'subnet'; -} - -sub private { - return $defaultData; -} - -sub properties { - return { - vnet => { - type => 'string', - description => "associated vnet", - }, - gateway => { - type => 'string', format => 'ip', - description => "Subnet Gateway: Will be assign on vnet for layer3 zones", - }, - snat => { - type => 'boolean', - description => "enable masquerade for this subnet if pve-firewall", - }, -# #cloudinit, dhcp options -# routes => { -# type => 'string', -# description => "static routes [network=:gateway=,network=:gateway=,... ]", -# }, - dnszoneprefix => { - type => 'string', format => 'dns-name', - description => "dns domain zone prefix ex: 'adm' -> .adm.mydomain.com", - }, - }; -} - -sub options { - return { - vnet => { optional => 0 }, - gateway => { optional => 1 }, -# routes => { optional => 1 }, - snat => { optional => 1 }, - dnszoneprefix => { optional => 1 }, - }; -} - -sub on_update_hook { - my ($class, $zone, $subnetid, $subnet, $old_subnet) = @_; - - my $cidr = $subnet->{cidr}; - my $mask = $subnet->{mask}; - - my $subnet_matcher = subnet_matcher($cidr); - - my $vnetid = $subnet->{vnet}; - my $gateway = $subnet->{gateway}; - my $ipam = $zone->{ipam}; - my $dns = $zone->{dns}; - my $dnszone = $zone->{dnszone}; - my $reversedns = $zone->{reversedns}; - - my $old_gateway = $old_subnet->{gateway} if $old_subnet; - my $mac = undef; - - if($vnetid) { - my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid); - raise_param_exc({ vnet => "$vnetid don't exist"}) if !$vnet; - raise_param_exc({ vnet => "you can't add a subnet on a vlanaware vnet"}) if $vnet->{vlanaware}; - $mac = $vnet->{mac}; - } - - my $pointopoint = 1 if Net::IP::ip_is_ipv4($gateway) && $mask == 32; - - #for /32 pointopoint, we allow gateway outside the subnet - raise_param_exc({ gateway => "$gateway is not in subnet $cidr"}) if $gateway && !$subnet_matcher->($gateway) && !$pointopoint; - - - if ($ipam) { - PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet); - - #don't register gateway for pointopoint - return if $pointopoint; - - #delete gateway on removal - if (!defined($gateway) && $old_gateway) { - eval { - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway); - }; - warn if $@; - } - if(!$old_gateway || $gateway && $gateway ne $old_gateway) { - my $hostname = "$vnetid-gw"; - my $description = "gateway"; - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, $description, 1); - } - - #delete old gateway after update - if($gateway && $old_gateway && $gateway ne $old_gateway) { - eval { - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway); - }; - warn if $@; - } - } -} - -sub on_delete_hook { - my ($class, $subnetid, $subnet_cfg, $vnet_cfg) = @_; - - return; -} - -1; diff --git a/PVE/Network/SDN/Subnets.pm b/PVE/Network/SDN/Subnets.pm deleted file mode 100644 index 6bb42e5..0000000 --- a/PVE/Network/SDN/Subnets.pm +++ /dev/null @@ -1,375 +0,0 @@ -package PVE::Network::SDN::Subnets; - -use strict; -use warnings; - -use Net::Subnet qw(subnet_matcher); -use Net::IP; -use NetAddr::IP qw(:lower); - -use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); -use PVE::Network::SDN::Dns; -use PVE::Network::SDN::Ipams; - -use PVE::Network::SDN::SubnetPlugin; -PVE::Network::SDN::SubnetPlugin->register(); -PVE::Network::SDN::SubnetPlugin->init(); - -sub sdn_subnets_config { - my ($cfg, $id, $noerr) = @_; - - die "no sdn subnet ID specified\n" if !$id; - - my $scfg = $cfg->{ids}->{$id}; - die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg); - - if($scfg) { - my ($zone, $network, $mask) = split(/-/, $id); - $scfg->{cidr} = "$network/$mask"; - $scfg->{zone} = $zone; - $scfg->{network} = $network; - $scfg->{mask} = $mask; - } - - return $scfg; -} - -sub config { - my $config = cfs_read_file("sdn/subnets.cfg"); -} - -sub write_config { - my ($cfg) = @_; - - cfs_write_file("sdn/subnets.cfg", $cfg); -} - -sub sdn_subnets_ids { - my ($cfg) = @_; - - return sort keys %{$cfg->{ids}}; -} - -sub complete_sdn_subnet { - my ($cmdname, $pname, $cvalue) = @_; - - my $cfg = PVE::Network::SDN::Subnets::config(); - - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ]; -} - -sub get_subnet { - my ($subnetid, $running) = @_; - - my $cfg = {}; - if($running) { - my $cfg = PVE::Network::SDN::running_config(); - $cfg = $cfg->{subnets}; - } else { - $cfg = PVE::Network::SDN::Subnets::config(); - } - - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1); - return $subnet; -} - -sub find_ip_subnet { - my ($ip, $mask, $subnets) = @_; - - my $subnet = undef; - my $subnetid = undef; - - foreach my $id (sort keys %{$subnets}) { - - next if $mask ne $subnets->{$id}->{mask}; - my $cidr = $subnets->{$id}->{cidr}; - my $subnet_matcher = subnet_matcher($cidr); - next if !$subnet_matcher->($ip); - $subnet = $subnets->{$id}; - $subnetid = $id; - last; - } - die "can't find any subnet for ip $ip" if !$subnet; - - return ($subnetid, $subnet); -} - -sub verify_dns_zone { - 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); -} - -sub get_reversedns_zone { - my ($subnetid, $subnet, $dns, $ip) = @_; - - return if !$subnetid || !$dns || !$ip; - - 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->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip); -} - -sub add_dns_record { - my ($zone, $dns, $hostname, $ip) = @_; - return if !$zone || !$dns || !$hostname || !$ip; - - 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->add_a_record($plugin_config, $zone, $hostname, $ip); - -} - -sub add_dns_ptr_record { - my ($reversezone, $zone, $dns, $hostname, $ip) = @_; - - return if !$zone || !$reversezone || !$dns || !$hostname || !$ip; - - $hostname .= ".$zone"; - 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->add_ptr_record($plugin_config, $reversezone, $hostname, $ip); -} - -sub del_dns_record { - my ($zone, $dns, $hostname, $ip) = @_; - - return if !$zone || !$dns || !$hostname || !$ip; - - 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->del_a_record($plugin_config, $zone, $hostname, $ip); -} - -sub del_dns_ptr_record { - my ($reversezone, $dns, $ip) = @_; - - return if !$reversezone || !$dns || !$ip; - - 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->del_ptr_record($plugin_config, $reversezone, $ip); -} - -sub add_subnet { - my ($zone, $subnetid, $subnet) = @_; - - my $ipam = $zone->{ipam}; - return if !$ipam; - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $plugin_config = $ipam_cfg->{ids}->{$ipam}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - $plugin->add_subnet($plugin_config, $subnetid, $subnet); -} - -sub del_subnet { - my ($zone, $subnetid, $subnet) = @_; - - my $ipam = $zone->{ipam}; - return if !$ipam; - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $plugin_config = $ipam_cfg->{ids}->{$ipam}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - $plugin->del_subnet($plugin_config, $subnetid, $subnet); -} - -sub next_free_ip { - my ($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns) = @_; - - my $cidr = undef; - my $ip = undef; - $description = '' if !$description; - - my $ipamid = $zone->{ipam}; - my $dns = $zone->{dns}; - my $dnszone = $zone->{dnszone}; - my $reversedns = $zone->{reversedns}; - my $dnszoneprefix = $subnet->{dnszoneprefix}; - - $hostname .= ".$dnszoneprefix" if $dnszoneprefix; - - #verify dns zones before ipam - verify_dns_zone($dnszone, $dns) if !$skipdns; - - 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}); - eval { - $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description); - ($ip, undef) = split(/\//, $cidr); - }; - die $@ if $@; - } - - eval { - my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); - - if(!$skipdns) { - #add dns - add_dns_record($dnszone, $dns, $hostname, $ip); - #add reverse dns - add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip); - } - }; - if ($@) { - #rollback - my $err = $@; - eval { - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname) - }; - die $err; - } - return $cidr; -} - -sub add_ip { - my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $skipdns) = @_; - - return if !$subnet || !$ip; - - my $ipaddr = NetAddr::IP->new($ip); - $ip = $ipaddr->canon(); - - my $ipamid = $zone->{ipam}; - my $dns = $zone->{dns}; - my $dnszone = $zone->{dnszone}; - my $reversedns = $zone->{reversedns}; - my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); - my $dnszoneprefix = $subnet->{dnszoneprefix}; - - $hostname .= ".$dnszoneprefix" if $dnszoneprefix; - - #verify dns zones before ipam - if(!$skipdns) { - 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}); - - eval { - $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway); - }; - die $@ if $@; - } - - eval { - if(!$skipdns) { - #add dns - add_dns_record($dnszone, $dns, $hostname, $ip); - #add reverse dns - add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip); - } - }; - if ($@) { - #rollback - my $err = $@; - eval { - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname) - }; - die $err; - } -} - -sub update_ip { - my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns) = @_; - - return if !$subnet || !$ip; - - my $ipaddr = NetAddr::IP->new($ip); - $ip = $ipaddr->canon(); - - my $ipamid = $zone->{ipam}; - my $dns = $zone->{dns}; - my $dnszone = $zone->{dnszone}; - my $reversedns = $zone->{reversedns}; - my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); - my $dnszoneprefix = $subnet->{dnszoneprefix}; - - $hostname .= ".$dnszoneprefix" if $dnszoneprefix; - - #verify dns zones before ipam - if(!$skipdns) { - 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}); - eval { - $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description); - }; - die $@ if $@; - } - - return if $hostname eq $oldhostname; - - eval { - if(!$skipdns) { - #add dns - del_dns_record($dnszone, $dns, $oldhostname, $ip); - add_dns_record($dnszone, $dns, $hostname, $ip); - #add reverse dns - del_dns_ptr_record($reversednszone, $reversedns, $ip); - add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip); - } - }; -} - -sub del_ip { - my ($zone, $subnetid, $subnet, $ip, $hostname, $skipdns) = @_; - - return if !$subnet || !$ip; - - my $ipaddr = NetAddr::IP->new($ip); - $ip = $ipaddr->canon(); - - my $ipamid = $zone->{ipam}; - my $dns = $zone->{dns}; - my $dnszone = $zone->{dnszone}; - my $reversedns = $zone->{reversedns}; - my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); - my $dnszoneprefix = $subnet->{dnszoneprefix}; - $hostname .= ".$dnszoneprefix" if $dnszoneprefix; - - if(!$skipdns) { - 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, $subnet, $ip); - } - - eval { - if(!$skipdns) { - del_dns_record($dnszone, $dns, $hostname, $ip); - del_dns_ptr_record($reversednszone, $reversedns, $ip); - } - }; - if ($@) { - warn $@; - } -} - -1; diff --git a/PVE/Network/SDN/VnetPlugin.pm b/PVE/Network/SDN/VnetPlugin.pm deleted file mode 100644 index 062904c..0000000 --- a/PVE/Network/SDN/VnetPlugin.pm +++ /dev/null @@ -1,109 +0,0 @@ -package PVE::Network::SDN::VnetPlugin; - -use strict; -use warnings; - -use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); -use PVE::Exception qw(raise raise_param_exc); -use PVE::JSONSchema qw(get_standard_option); - -use PVE::SectionConfig; -use base qw(PVE::SectionConfig); - -PVE::Cluster::cfs_register_file('sdn/vnets.cfg', - sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_config(@_); }); - -PVE::JSONSchema::register_standard_option('pve-sdn-vnet-id', { - description => "The SDN vnet object identifier.", - type => 'string', format => 'pve-sdn-vnet-id', -}); - -PVE::JSONSchema::register_format('pve-sdn-vnet-id', \&parse_sdn_vnet_id); -sub parse_sdn_vnet_id { - my ($id, $noerr) = @_; - - if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { - return undef if $noerr; - die "vnet ID '$id' contains illegal characters\n"; - } - die "vnet ID '$id' can't be more length than 8 characters\n" if length($id) > 8; - return $id; -} - -my $defaultData = { - - propertyList => { - vnet => get_standard_option('pve-sdn-vnet-id', - { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }), - }, -}; - -sub type { - return 'vnet'; -} - -sub private { - return $defaultData; -} - -sub properties { - return { - zone => { - type => 'string', - description => "zone id", - }, - type => { - description => "Type", - optional => 1, - }, - tag => { - type => 'integer', - description => "vlan or vxlan id", - }, - vlanaware => { - type => 'boolean', - description => 'Allow vm VLANs to pass through this vnet.', - }, - alias => { - type => 'string', - description => "alias name of the vnet", - pattern => qr/[\(\)-_.\w\d\s]{0,256}/i, - maxLength => 256, - optional => 1, - }, - }; -} - -sub options { - return { - zone => { optional => 0}, - tag => { optional => 1}, - alias => { optional => 1 }, - vlanaware => { optional => 1 }, - }; -} - -sub on_delete_hook { - my ($class, $vnetid, $vnet_cfg) = @_; - - #verify if subnets are associated - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid); - raise_param_exc({ vnet => "Can't delete vnet if subnets exists"}) if $subnets; -} - -sub on_update_hook { - my ($class, $vnetid, $vnet_cfg) = @_; - - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - my $tag = $vnet->{tag}; - my $vlanaware = $vnet->{vlanaware}; - - #don't allow vlanaware change if subnets are defined - if($vnet->{vlanaware}) { - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid); - raise_param_exc({ vlanaware => "vlanaware vnet is not compatible with subnets"}) if $subnets; - } -} - -1; diff --git a/PVE/Network/SDN/Vnets.pm b/PVE/Network/SDN/Vnets.pm deleted file mode 100644 index 0b32c58..0000000 --- a/PVE/Network/SDN/Vnets.pm +++ /dev/null @@ -1,163 +0,0 @@ -package PVE::Network::SDN::Vnets; - -use strict; -use warnings; - -use Net::IP; - -use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); -use PVE::Network::SDN; -use PVE::Network::SDN::Subnets; -use PVE::Network::SDN::Zones; - -use PVE::Network::SDN::VnetPlugin; -PVE::Network::SDN::VnetPlugin->register(); -PVE::Network::SDN::VnetPlugin->init(); - -sub sdn_vnets_config { - my ($cfg, $id, $noerr) = @_; - - die "no sdn vnet ID specified\n" if !$id; - - my $scfg = $cfg->{ids}->{$id}; - die "sdn vnet '$id' does not exist\n" if (!$noerr && !$scfg); - - return $scfg; -} - -sub config { - return cfs_read_file("sdn/vnets.cfg"); -} - -sub write_config { - my ($cfg) = @_; - - cfs_write_file("sdn/vnets.cfg", $cfg); -} - -sub sdn_vnets_ids { - my ($cfg) = @_; - - return sort keys %{$cfg->{ids}}; -} - -sub complete_sdn_vnet { - my ($cmdname, $pname, $cvalue) = @_; - - my $cfg = PVE::Network::SDN::Vnets::config(); - - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg) ]; -} - -sub get_vnet { - my ($vnetid, $running) = @_; - - return if !$vnetid; - - my $scfg = {}; - if($running) { - my $cfg = PVE::Network::SDN::running_config(); - $scfg = $cfg->{vnets}; - } else { - $scfg = PVE::Network::SDN::Vnets::config(); - } - - my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($scfg, $vnetid, 1); - - return $vnet; -} - -sub get_subnets { - my ($vnetid) = @_; - - return if !$vnetid; - - my $subnets = undef; - my $subnets_cfg = PVE::Network::SDN::Subnets::config(); - foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) { - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); - next if !$subnet->{vnet} || $subnet->{vnet} ne $vnetid; - $subnets->{$subnetid} = $subnet; - } - return $subnets; - -} - -sub get_subnet_from_vnet_cidr { - my ($vnetid, $cidr) = @_; - - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); - my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid); - my $zoneid = $vnet->{zone}; - my $zone = PVE::Network::SDN::Zones::get_zone($zoneid); - - my ($ip, $mask) = split(/\//, $cidr); - die "ip address is not in cidr format" if !$mask; - - my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $mask, $subnets); - - return ($zone, $subnetid, $subnet, $ip); -} - -sub get_next_free_cidr { - my ($vnetid, $hostname, $mac, $description, $ipversion, $skipdns) = @_; - - my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid); - my $zoneid = $vnet->{zone}; - my $zone = PVE::Network::SDN::Zones::get_zone($zoneid); - - return if !$zone->{ipam}; - - $ipversion = 4 if !$ipversion; - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); - my $ip = undef; - my $subnetcount = 0; - - foreach my $subnetid (sort keys %{$subnets}) { - my $subnet = $subnets->{$subnetid}; - my $network = $subnet->{network}; - - next if $ipversion != Net::IP::ip_get_version($network); - $subnetcount++; - - eval { - $ip = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns); - }; - warn $@ if $@; - last if $ip; - } - die "can't find any free ip" if !$ip && $subnetcount > 0; - - return $ip; -} - -sub add_cidr { - my ($vnetid, $cidr, $hostname, $mac, $description, $skipdns) = @_; - - return if !$vnetid; - - my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr); - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, undef, $skipdns); -} - -sub update_cidr { - my ($vnetid, $cidr, $hostname, $oldhostname, $mac, $description, $skipdns) = @_; - - return if !$vnetid; - - my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr); - PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns); -} - -sub del_cidr { - my ($vnetid, $cidr, $hostname, $skipdns) = @_; - - return if !$vnetid; - - my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr); - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $skipdns); -} - - - -1; diff --git a/PVE/Network/SDN/Zones.pm b/PVE/Network/SDN/Zones.pm deleted file mode 100644 index f8e40b1..0000000 --- a/PVE/Network/SDN/Zones.pm +++ /dev/null @@ -1,357 +0,0 @@ -package PVE::Network::SDN::Zones; - -use strict; -use warnings; - -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::Vnets; -use PVE::Network::SDN::Zones::VlanPlugin; -use PVE::Network::SDN::Zones::QinQPlugin; -use PVE::Network::SDN::Zones::VxlanPlugin; -use PVE::Network::SDN::Zones::EvpnPlugin; -use PVE::Network::SDN::Zones::FaucetPlugin; -use PVE::Network::SDN::Zones::SimplePlugin; -use PVE::Network::SDN::Zones::Plugin; - -PVE::Network::SDN::Zones::VlanPlugin->register(); -PVE::Network::SDN::Zones::QinQPlugin->register(); -PVE::Network::SDN::Zones::VxlanPlugin->register(); -PVE::Network::SDN::Zones::EvpnPlugin->register(); -PVE::Network::SDN::Zones::FaucetPlugin->register(); -PVE::Network::SDN::Zones::SimplePlugin->register(); -PVE::Network::SDN::Zones::Plugin->init(); - -my $local_network_sdn_file = "/etc/network/interfaces.d/sdn"; - -sub sdn_zones_config { - my ($cfg, $id, $noerr) = @_; - - die "no sdn zone 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/zones.cfg"); - return $config; -} - -sub get_plugin_config { - my ($vnet) = @_; - my $zoneid = $vnet->{zone}; - my $zone_cfg = PVE::Network::SDN::Zones::config(); - return $zone_cfg->{ids}->{$zoneid}; -} - -sub write_config { - my ($cfg) = @_; - - cfs_write_file("sdn/zones.cfg", $cfg); -} - -sub sdn_zones_ids { - my ($cfg) = @_; - - return sort keys %{$cfg->{ids}}; -} - -sub complete_sdn_zone { - my ($cmdname, $pname, $cvalue) = @_; - - my $cfg = PVE::Network::SDN::running_config(); - - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ]; -} - -sub get_zone { - my ($zoneid, $running) = @_; - - my $cfg = {}; - if($running) { - my $cfg = PVE::Network::SDN::running_config(); - $cfg = $cfg->{vnets}; - } else { - $cfg = PVE::Network::SDN::Zones::config(); - } - - my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1); - - return $zone; -} - - -sub generate_etc_network_config { - - my $cfg = PVE::Network::SDN::running_config(); - - my $version = $cfg->{version}; - my $vnet_cfg = $cfg->{vnets}; - my $zone_cfg = $cfg->{zones}; - my $subnet_cfg = $cfg->{subnets}; - my $controller_cfg = $cfg->{controllers}; - return if !$vnet_cfg && !$zone_cfg; - - my $interfaces_config = PVE::INotify::read_file('interfaces'); - - #generate configuration - my $config = {}; - my $nodename = PVE::INotify::nodename(); - - for my $id (sort keys %{$vnet_cfg->{ids}}) { - my $vnet = $vnet_cfg->{ids}->{$id}; - my $zone = $vnet->{zone}; - - if (!$zone) { - warn "can't generate vnet '$id': no zone assigned!\n"; - next; - } - - my $plugin_config = $zone_cfg->{ids}->{$zone}; - - if (!defined($plugin_config)) { - warn "can't generate vnet '$id': zone $zone don't exist\n"; - next; - } - - next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename}; - - my $controller; - if (my $controllerid = $plugin_config->{controller}) { - $controller = $controller_cfg->{ids}->{$controllerid}; - } - - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - eval { - $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config); - }; - if (my $err = $@) { - warn "zone $zone : vnet $id : $err\n"; - next; - } - } - - my $raw_network_config = "\#version:$version\n"; - foreach my $iface (sort keys %$config) { - $raw_network_config .= "\n"; - $raw_network_config .= "auto $iface\n"; - $raw_network_config .= "iface $iface\n"; - foreach my $option (@{$config->{$iface}}) { - $raw_network_config .= "\t$option\n"; - } - } - - return $raw_network_config; -} - -sub write_etc_network_config { - my ($rawconfig) = @_; - - return if !$rawconfig; - - my $writefh = IO::File->new($local_network_sdn_file,">"); - print $writefh $rawconfig; - $writefh->close(); -} - -sub read_etc_network_config_version { - my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file); - - return if !defined($versionstr); - - if ($versionstr =~ m/^\#version:(\d+)$/) { - return $1; - } -} - -sub ifquery_check { - - my $cmd = ['ifquery', '-a', '-c', '-o','json']; - - my $result = ''; - my $reader = sub { $result .= shift }; - - eval { - run_command($cmd, outfunc => $reader); - }; - - my $resultjson = decode_json($result); - my $interfaces = {}; - - foreach my $interface (@$resultjson) { - my $name = $interface->{name}; - $interfaces->{$name} = { - status => $interface->{status}, - config => $interface->{config}, - config_status => $interface->{config_status}, - }; - } - - return $interfaces; -} - -my $warned_about_reload; - -sub status { - - my $err_config = undef; - - my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version(); - my $cfg = PVE::Network::SDN::running_config(); - my $sdn_version = $cfg->{version}; - - return if !$sdn_version; - - if (!$local_version) { - $err_config = "local sdn network configuration is not yet generated, please reload"; - if (!$warned_about_reload) { - $warned_about_reload = 1; - warn "$err_config\n"; - } - } elsif ($local_version < $sdn_version) { - $err_config = "local sdn network configuration is too old, please reload"; - if (!$warned_about_reload) { - $warned_about_reload = 1; - warn "$err_config\n"; - } - } else { - $warned_about_reload = 0; - } - - my $status = ifquery_check(); - - my $vnet_cfg = $cfg->{vnets}; - my $zone_cfg = $cfg->{zones}; - my $nodename = PVE::INotify::nodename(); - - my $vnet_status = {}; - my $zone_status = {}; - - for my $id (sort keys %{$zone_cfg->{ids}}) { - next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename}; - $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available'; - } - - foreach my $id (sort keys %{$vnet_cfg->{ids}}) { - my $vnet = $vnet_cfg->{ids}->{$id}; - my $zone = $vnet->{zone}; - next if !defined($zone); - - my $plugin_config = $zone_cfg->{ids}->{$zone}; - - if (!defined($plugin_config)) { - $vnet_status->{$id}->{status} = 'error'; - $vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured"; - next; - } - - next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename}; - - $vnet_status->{$id}->{zone} = $zone; - $vnet_status->{$id}->{status} = 'available'; - - if ($err_config) { - $vnet_status->{$id}->{status} = 'pending'; - $vnet_status->{$id}->{statusmsg} = $err_config; - next; - } - - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status); - if (@{$err_msg} > 0) { - $vnet_status->{$id}->{status} = 'error'; - $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg}); - $zone_status->{$id}->{status} = 'error'; - } - } - - return ($zone_status, $vnet_status); -} - -sub tap_create { - my ($iface, $bridge) = @_; - - my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); - if (!$vnet) { # fallback for classic bridge - PVE::Network::tap_create($iface, $bridge); - return; - } - - my $plugin_config = get_plugin_config($vnet); - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - $plugin->tap_create($plugin_config, $vnet, $iface, $bridge); -} - -sub veth_create { - my ($veth, $vethpeer, $bridge, $hwaddr) = @_; - - my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); - if (!$vnet) { # fallback for classic bridge - PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr); - return; - } - - my $plugin_config = get_plugin_config($vnet); - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr); -} - -sub tap_plug { - my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_; - - my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); - if (!$vnet) { # fallback for classic bridge - my $interfaces_config = PVE::INotify::read_file('interfaces'); - my $opts = {}; - $opts->{learning} = 0 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'}; - PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts); - return; - } - - my $plugin_config = get_plugin_config($vnet); - my $nodename = PVE::INotify::nodename(); - - die "vnet $bridge is not allowed on this node\n" - if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename}); - - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate); -} - -sub add_bridge_fdb { - my ($iface, $macaddr, $bridge, $firewall) = @_; - - my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); - if (!$vnet) { # fallback for classic bridge - PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall); - return; - } - - my $plugin_config = get_plugin_config($vnet); - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'}; -} - -sub del_bridge_fdb { - my ($iface, $macaddr, $bridge, $firewall) = @_; - - my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); - if (!$vnet) { # fallback for classic bridge - PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall); - return; - } - - my $plugin_config = get_plugin_config($vnet); - my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); - PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'}; -} - -1; - diff --git a/PVE/Network/SDN/Zones/EvpnPlugin.pm b/PVE/Network/SDN/Zones/EvpnPlugin.pm deleted file mode 100644 index a5a7539..0000000 --- a/PVE/Network/SDN/Zones/EvpnPlugin.pm +++ /dev/null @@ -1,315 +0,0 @@ -package PVE::Network::SDN::Zones::EvpnPlugin; - -use strict; -use warnings; -use PVE::Network::SDN::Zones::VxlanPlugin; -use PVE::Exception qw(raise raise_param_exc); -use PVE::JSONSchema qw(get_standard_option); -use PVE::Tools qw($IPV4RE); -use PVE::INotify; -use PVE::Cluster; -use PVE::Tools; -use Net::IP; - -use PVE::Network::SDN::Controllers::EvpnPlugin; - -use base('PVE::Network::SDN::Zones::VxlanPlugin'); - -sub type { - return 'evpn'; -} - -PVE::JSONSchema::register_format('pve-sdn-bgp-rt', \&pve_verify_sdn_bgp_rt); -sub pve_verify_sdn_bgp_rt { - my ($rt) = @_; - - if ($rt =~ m/^(\d+):(\d+)$/) { - my $asn = $1; - my $id = $2; - - if ($asn < 0 || $asn > 4294967295) { - die "value does not look like a valid bgp route-target\n"; - } - if ($id < 0 || $id > 4294967295) { - die "value does not look like a valid bgp route-target\n"; - } - } else { - die "value does not look like a valid bgp route-target\n"; - } - return $rt; -} - -sub properties { - return { - 'vrf-vxlan' => { - type => 'integer', - description => "l3vni.", - }, - 'controller' => { - type => 'string', - description => "Frr router name", - }, - 'mac' => { - type => 'string', - description => "Anycast logical router mac address", - optional => 1, format => 'mac-addr' - }, - 'exitnodes' => get_standard_option('pve-node-list'), - 'exitnodes-local-routing' => { - type => 'boolean', - description => "Allow exitnodes to connect to evpn guests", - optional => 1 - }, - 'exitnodes-primary' => get_standard_option('pve-node', { - description => "Force traffic to this exitnode first."}), - 'advertise-subnets' => { - type => 'boolean', - description => "Advertise evpn subnets if you have silent hosts", - optional => 1 - }, - 'disable-arp-nd-suppression' => { - type => 'boolean', - description => "Disable ipv4 arp && ipv6 neighbour discovery suppression", - optional => 1 - }, - 'rt-import' => { - type => 'string', - description => "Route-Target import", - optional => 1, format => 'pve-sdn-bgp-rt-list' - } - }; -} - -sub options { - return { - nodes => { optional => 1}, - 'vrf-vxlan' => { optional => 0 }, - controller => { optional => 0 }, - exitnodes => { optional => 1 }, - 'exitnodes-local-routing' => { optional => 1 }, - 'exitnodes-primary' => { optional => 1 }, - 'advertise-subnets' => { optional => 1 }, - 'disable-arp-nd-suppression' => { optional => 1 }, - 'rt-import' => { optional => 1 }, - mtu => { optional => 1 }, - mac => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; - - my $tag = $vnet->{tag}; - my $alias = $vnet->{alias}; - my $mac = $plugin_config->{'mac'}; - - my $vrf_iface = "vrf_$zoneid"; - my $vrfvxlan = $plugin_config->{'vrf-vxlan'}; - my $local_node = PVE::INotify::nodename(); - - die "missing vxlan tag" if !$tag; - die "missing controller" if !$controller; - warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware}; - - my @peers = PVE::Tools::split_list($controller->{'peers'}); - my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg); - my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; - my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); - my $is_evpn_gateway = $plugin_config->{'exitnodes'}->{$local_node}; - my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'}; - - - my $mtu = 1450; - $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu}; - $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; - - #vxlan interface - my $vxlan_iface = "vxlan_$vnetid"; - my @iface_config = (); - push @iface_config, "vxlan-id $tag"; - push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip; - push @iface_config, "bridge-learning off"; - push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'}; - - push @iface_config, "mtu $mtu" if $mtu; - push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface}; - - #vnet bridge - @iface_config = (); - - my $address = {}; - my $ipv4 = undef; - my $ipv6 = undef; - my $enable_forward_v4 = undef; - my $enable_forward_v6 = undef; - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); - foreach my $subnetid (sort keys %{$subnets}) { - my $subnet = $subnets->{$subnetid}; - my $cidr = $subnet->{cidr}; - my $mask = $subnet->{mask}; - - my $gateway = $subnet->{gateway}; - if ($gateway) { - push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway}); - $address->{$gateway} = 1; - } - - my $iptables = undef; - my $checkrouteip = undef; - my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4; - - if ($ipversion == 6) { - $ipv6 = 1; - $iptables = "ip6tables"; - $checkrouteip = '2001:4860:4860::8888'; - $enable_forward_v6 = 1 if $gateway; - } else { - $ipv4 = 1; - $iptables = "iptables"; - $checkrouteip = '8.8.8.8'; - $enable_forward_v4 = 1 if $gateway; - } - - if ($subnet->{snat}) { - - #find outgoing interface - my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip); - if ($outip && $outiface && $is_evpn_gateway) { - #use snat, faster than masquerade - push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; - push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; - #add conntrack zone once on outgoing interface - push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1"; - push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1"; - } - } - } - - push @iface_config, "hwaddress $mac" if $mac; - push @iface_config, "bridge_ports $vxlan_iface"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "alias $alias" if $alias; - push @iface_config, "ip-forward on" if $enable_forward_v4; - push @iface_config, "ip6-forward on" if $enable_forward_v6; - push @iface_config, "arp-accept on" if $ipv4||$ipv6; - push @iface_config, "vrf $vrf_iface" if $vrf_iface; - push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; - - if ($vrf_iface) { - #vrf interface - @iface_config = (); - push @iface_config, "vrf-table auto"; - if(!$is_evpn_gateway) { - push @iface_config, "post-up ip route add vrf $vrf_iface unreachable default metric 4278198272"; - } else { - push @iface_config, "post-up ip route del vrf $vrf_iface unreachable default metric 4278198272"; - } - - push(@{$config->{$vrf_iface}}, @iface_config) if !$config->{$vrf_iface}; - - if ($vrfvxlan) { - #l3vni vxlan interface - my $iface_vrf_vxlan = "vrfvx_$zoneid"; - @iface_config = (); - push @iface_config, "vxlan-id $vrfvxlan"; - push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip; - push @iface_config, "bridge-learning off"; - push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'}; - push @iface_config, "mtu $mtu" if $mtu; - push(@{$config->{$iface_vrf_vxlan}}, @iface_config) if !$config->{$iface_vrf_vxlan}; - - #l3vni bridge - my $brvrf = "vrfbr_$zoneid"; - @iface_config = (); - push @iface_config, "bridge-ports $iface_vrf_vxlan"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "vrf $vrf_iface"; - push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf}; - } - - if ( $is_evpn_gateway && $exitnodes_local_routing ) { - #add a veth pair for local cross-vrf routing - my $iface_xvrf = "xvrf_$zoneid"; - my $iface_xvrfp = "xvrfp_$zoneid"; - - @iface_config = (); - push @iface_config, "link-type veth"; - push @iface_config, "address 10.255.255.1/30"; - push @iface_config, "veth-peer-name $iface_xvrfp"; - push @iface_config, "mtu ".($mtu+50) if $mtu; - push(@{$config->{$iface_xvrf}}, @iface_config) if !$config->{$iface_xvrf}; - - @iface_config = (); - push @iface_config, "link-type veth"; - push @iface_config, "address 10.255.255.2/30"; - push @iface_config, "veth-peer-name $iface_xvrf"; - push @iface_config, "vrf $vrf_iface"; - push @iface_config, "mtu ".($mtu+50) if $mtu; - push(@{$config->{$iface_xvrfp}}, @iface_config) if !$config->{$iface_xvrfp}; - } - } - return $config; -} - -sub on_update_hook { - my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_; - - # verify that controller exist - my $controller = $zone_cfg->{ids}->{$zoneid}->{controller}; - if (!defined($controller_cfg->{ids}->{$controller})) { - die "controller $controller don't exist"; - } else { - die "$controller is not a evpn controller type" if $controller_cfg->{ids}->{$controller}->{type} ne 'evpn'; - } - - #vrf-vxlan need to be defined - - my $vrfvxlan = $zone_cfg->{ids}->{$zoneid}->{'vrf-vxlan'}; - # verify that vrf-vxlan is not already declared in another zone - foreach my $id (keys %{$zone_cfg->{ids}}) { - next if $id eq $zoneid; - die "vrf-vxlan $vrfvxlan is already declared in $id" - if (defined($zone_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $zone_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan); - } - - if (!defined($zone_cfg->{ids}->{$zoneid}->{'mac'})) { - my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); - $zone_cfg->{ids}->{$zoneid}->{'mac'} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); - } -} - - -sub vnet_update_hook { - my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; - - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - my $tag = $vnet->{tag}; - - raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag); - raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216; - - # verify that tag is not already defined globally (vxlan-id are unique) - foreach my $id (keys %{$vnet_cfg->{ids}}) { - next if $id eq $vnetid; - my $othervnet = $vnet_cfg->{ids}->{$id}; - my $other_tag = $othervnet->{tag}; - my $other_zoneid = $othervnet->{zone}; - my $other_zone = $zone_cfg->{ids}->{$other_zoneid}; - next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn'; - raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag; - } -} - - -1; - - diff --git a/PVE/Network/SDN/Zones/FaucetPlugin.pm b/PVE/Network/SDN/Zones/FaucetPlugin.pm deleted file mode 100644 index a237d17..0000000 --- a/PVE/Network/SDN/Zones/FaucetPlugin.pm +++ /dev/null @@ -1,74 +0,0 @@ -package PVE::Network::SDN::Zones::FaucetPlugin; - -use strict; -use warnings; -use PVE::Network::SDN::Zones::VlanPlugin; - -use base('PVE::Network::SDN::Zones::VlanPlugin'); - -sub type { - return 'faucet'; -} - -sub properties { - return { - 'dp-id' => { - type => 'integer', - description => 'Faucet dataplane id', - }, - }; -} - -sub options { - - return { - nodes => { optional => 1}, - 'dp-id' => { optional => 0 }, -# 'uplink-id' => { optional => 0 }, - 'controller' => { optional => 0 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $controller, $config) = @_; - - my $mtu = $vnet->{mtu}; - my $uplink = $plugin_config->{'uplink-id'}; - my $dpid = $plugin_config->{'dp-id'}; - my $dphex = printf("%x",$dpid); #fixme :should be 16characters hex - - my $iface = $uplinks->{$uplink}->{name}; - $iface = "uplink${uplink}" if !$iface; - - #tagged interface - my @iface_config = (); - push @iface_config, "ovs_type OVSPort"; - push @iface_config, "ovs_bridge $zoneid"; - push @iface_config, "ovs_mtu $mtu" if $mtu; - push(@{$config->{$iface}}, @iface_config) if !$config->{$iface}; - - #vnet bridge - @iface_config = (); - push @iface_config, "ovs_port $iface"; - push @iface_config, "ovs_type OVSBridge"; - push @iface_config, "ovs_mtu $mtu" if $mtu; - - push @iface_config, "ovs_extra set bridge $zoneid other-config:datapath-id=$dphex"; - push @iface_config, "ovs_extra set bridge $zoneid other-config:disable-in-band=true"; - push @iface_config, "ovs_extra set bridge $zoneid fail_mode=secure"; - push @iface_config, "ovs_extra set-controller $vnetid tcp:127.0.0.1:6653"; - - push(@{$config->{$zoneid}}, @iface_config) if !$config->{$zoneid}; - - return $config; -} - - -1; - - diff --git a/PVE/Network/SDN/Zones/Makefile b/PVE/Network/SDN/Zones/Makefile deleted file mode 100644 index 8454388..0000000 --- a/PVE/Network/SDN/Zones/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -SOURCES=Plugin.pm VlanPlugin.pm VxlanPlugin.pm FaucetPlugin.pm EvpnPlugin.pm QinQPlugin.pm SimplePlugin.pm - - -PERL5DIR=${DESTDIR}/usr/share/perl5 - -.PHONY: install -install: - for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Zones/$$i; done diff --git a/PVE/Network/SDN/Zones/Plugin.pm b/PVE/Network/SDN/Zones/Plugin.pm deleted file mode 100644 index 2c707b3..0000000 --- a/PVE/Network/SDN/Zones/Plugin.pm +++ /dev/null @@ -1,340 +0,0 @@ -package PVE::Network::SDN::Zones::Plugin; - -use strict; -use warnings; - -use PVE::Tools qw(run_command); -use PVE::JSONSchema; -use PVE::Cluster; -use PVE::Network; - -use PVE::JSONSchema qw(get_standard_option); -use base qw(PVE::SectionConfig); - -PVE::Cluster::cfs_register_file( - 'sdn/zones.cfg', - sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_config(@_); }, -); - -PVE::JSONSchema::register_standard_option('pve-sdn-zone-id', { - description => "The SDN zone object identifier.", - type => 'string', format => 'pve-sdn-zone-id', -}); - -PVE::JSONSchema::register_format('pve-sdn-zone-id', \&parse_sdn_zone_id); -sub parse_sdn_zone_id { - my ($id, $noerr) = @_; - - if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { - return undef if $noerr; - die "zone ID '$id' contains illegal characters\n"; - } - die "zone ID '$id' can't be more length than 8 characters\n" if length($id) > 8; - return $id; -} - -my $defaultData = { - - propertyList => { - type => { - description => "Plugin type.", - type => 'string', format => 'pve-configid', - type => 'string', - }, - nodes => get_standard_option('pve-node-list', { optional => 1 }), - zone => get_standard_option('pve-sdn-zone-id', { - completion => \&PVE::Network::SDN::Zones::complete_sdn_zone, - }), - ipam => { - type => 'string', - description => "use a specific ipam", - optional => 1, - }, - }, -}; - -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 decode_value { - my ($class, $type, $key, $value) = @_; - - if ($key eq 'nodes' || $key eq 'exitnodes') { - my $res = {}; - - foreach my $node (PVE::Tools::split_list($value)) { - if (PVE::JSONSchema::pve_verify_node_name($node)) { - $res->{$node} = 1; - } - } - - return $res; - } - - return $value; -} - -sub encode_value { - my ($class, $type, $key, $value) = @_; - - if ($key eq 'nodes' || $key eq 'exitnodes') { - return join(',', keys(%$value)); - } - - return $value; -} - -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; - - die "please implement inside plugin"; -} - -sub generate_controller_config { - my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_; - - die "please implement inside plugin"; -} - -sub generate_controller_vnet_config { - my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_; - -} - -sub write_controller_config { - my ($class, $plugin_config, $config) = @_; - - die "please implement inside plugin"; -} - -sub controller_reload { - my ($class) = @_; - - die "please implement inside plugin"; -} - -sub on_delete_hook { - my ($class, $zoneid, $vnet_cfg) = @_; - - # verify that no vnet are associated to this zone - foreach my $id (keys %{$vnet_cfg->{ids}}) { - my $vnet = $vnet_cfg->{ids}->{$id}; - die "zone $zoneid is used by vnet $id" - if ($vnet->{type} eq 'vnet' && defined($vnet->{zone}) && $vnet->{zone} eq $zoneid); - } -} - -sub on_update_hook { - my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_; - - # do nothing by default -} - -sub vnet_update_hook { - my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; - - # do nothing by default -} - -#helpers -sub parse_tag_number_or_range { - my ($str, $max, $tag) = @_; - - my @elements = split(/,/, $str); - my $count = 0; - my $allowed = undef; - - die "extraneous commas in list\n" if $str ne join(',', @elements); - foreach my $item (@elements) { - if ($item =~ m/^([0-9]+)-([0-9]+)$/) { - $count += 2; - my ($port1, $port2) = ($1, $2); - die "invalid port '$port1'\n" if $port1 > $max; - die "invalid port '$port2'\n" if $port2 > $max; - die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2; - - if ($tag && $tag >= $port1 && $tag <= $port2){ - $allowed = 1; - last; - } - - } elsif ($item =~ m/^([0-9]+)$/) { - $count += 1; - my $port = $1; - die "invalid port '$port'\n" if $port > $max; - - if ($tag && $tag == $port){ - $allowed = 1; - last; - } - } - } - die "tag $tag is not allowed" if $tag && !$allowed; - - return (scalar(@elements) > 1); -} - -sub status { - my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; - - my $err_msg = []; - - # ifaces to check - my $ifaces = [ $vnetid ]; - - foreach my $iface (@{$ifaces}) { - if (!$status->{$iface}->{status}) { - push @$err_msg, "missing $iface"; - } elsif ($status->{$iface}->{status} ne 'pass') { - push @$err_msg, "error $iface"; - } - } - return $err_msg; -} - - -sub tap_create { - my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_; - - PVE::Network::tap_create($iface, $vnetid); -} - -sub veth_create { - my ($class, $plugin_config, $vnet, $veth, $vethpeer, $vnetid, $hwaddr) = @_; - - PVE::Network::veth_create($veth, $vethpeer, $vnetid, $hwaddr); -} - -sub tap_plug { - my ($class, $plugin_config, $vnet, $tag, $iface, $vnetid, $firewall, $trunks, $rate) = @_; - - my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$vnetid/bridge/vlan_filtering"); - die "vm vlans are not allowed on vnet $vnetid" if !$vlan_aware && ($tag || $trunks); - - my $opts = {}; - $opts->{learning} = 0 if $plugin_config->{'bridge-disable-mac-learning'}; - PVE::Network::tap_plug($iface, $vnetid, $tag, $firewall, $trunks, $rate, $opts); -} - -#helper - -sub get_uplink_iface { - my ($interfaces_config, $uplink) = @_; - - my $iface = undef; - foreach my $id (keys %{$interfaces_config->{ifaces}}) { - my $interface = $interfaces_config->{ifaces}->{$id}; - if (my $iface_uplink = $interface->{'uplink-id'}) { - next if $iface_uplink ne $uplink; - if($interface->{type} ne 'eth' && $interface->{type} ne 'bond') { - warn "uplink $uplink is not a physical or bond interface"; - next; - } - $iface = $id; - } - } - - #create a dummy uplink interface if no uplink found - if(!$iface) { - warn "can't find uplink $uplink in physical interface"; - $iface = "uplink${uplink}"; - } - - return $iface; -} - -sub get_local_route_ip { - my ($targetip) = @_; - - my $ip = undef; - my $interface = undef; - - run_command(['/sbin/ip', 'route', 'get', $targetip], outfunc => sub { - if ($_[0] =~ m/src ($PVE::Tools::IPRE)/) { - $ip = $1; - } - if ($_[0] =~ m/dev (\S+)/) { - $interface = $1; - } - - }); - return ($ip, $interface); -} - - -sub find_local_ip_interface_peers { - my ($peers, $iface) = @_; - - my $network_config = PVE::INotify::read_file('interfaces'); - my $ifaces = $network_config->{ifaces}; - - #if iface is defined, return ip if exist (if not,try to find it on other ifaces) - if ($iface) { - my $ip = $ifaces->{$iface}->{address}; - return ($ip,$iface) if $ip; - } - - #is a local ip member of peers list ? - foreach my $address (@{$peers}) { - while (my $interface = each %$ifaces) { - my $ip = $ifaces->{$interface}->{address}; - if ($ip && $ip eq $address) { - return ($ip, $interface); - } - } - } - - #if peer is remote, find source with ip route - foreach my $address (@{$peers}) { - my ($ip, $interface) = get_local_route_ip($address); - return ($ip, $interface); - } -} - -sub find_bridge { - my ($bridge) = @_; - - die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge"; -} - -sub is_vlanaware { - my ($bridge) = @_; - - return PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering"); -} - -sub is_ovs { - my ($bridge) = @_; - - my $is_ovs = !-d "/sys/class/net/$bridge/brif"; - return $is_ovs; -} - -sub get_bridge_ifaces { - my ($bridge) = @_; - - my @bridge_ifaces = (); - my $dir = "/sys/class/net/$bridge/brif"; - PVE::Tools::dir_glob_foreach($dir, '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', sub { - push @bridge_ifaces, $_[0]; - }); - - return @bridge_ifaces; -} -1; diff --git a/PVE/Network/SDN/Zones/QinQPlugin.pm b/PVE/Network/SDN/Zones/QinQPlugin.pm deleted file mode 100644 index f4d12bc..0000000 --- a/PVE/Network/SDN/Zones/QinQPlugin.pm +++ /dev/null @@ -1,233 +0,0 @@ -package PVE::Network::SDN::Zones::QinQPlugin; - -use strict; -use warnings; - -use PVE::Exception qw(raise raise_param_exc); - -use PVE::Network::SDN::Zones::Plugin; - -use base('PVE::Network::SDN::Zones::Plugin'); - -sub type { - return 'qinq'; -} - -sub properties { - return { - tag => { - type => 'integer', - minimum => 0, - description => "Service-VLAN Tag", - }, - mtu => { - type => 'integer', - description => "MTU", - optional => 1, - }, - 'vlan-protocol' => { - type => 'string', - enum => ['802.1q', '802.1ad'], - default => '802.1q', - optional => 1, - } - }; -} - -sub options { - return { - nodes => { optional => 1}, - 'tag' => { optional => 0 }, - 'bridge' => { optional => 0 }, - 'bridge-disable-mac-learning' => { optional => 1 }, - 'mtu' => { optional => 1 }, - 'vlan-protocol' => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; - - my ($bridge, $mtu, $stag) = $plugin_config->@{'bridge', 'mtu', 'tag'}; - my $vlanprotocol = $plugin_config->{'vlan-protocol'}; - - PVE::Network::SDN::Zones::Plugin::find_bridge($bridge); - - my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); - my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); - - my @iface_config = (); - my $zone_notag_uplink = "ln_${zoneid}"; - my $zone_notag_uplinkpeer = "pr_${zoneid}"; - my $zone = "z_${zoneid}"; - - my $vnet_bridge_ports = ""; - if (my $ctag = $vnet->{tag}) { - $vnet_bridge_ports = "$zone.$ctag"; - } else { - $vnet_bridge_ports = $zone_notag_uplinkpeer; - } - - my $zone_bridge_ports = ""; - if ($is_ovs) { - # ovs--->ovsintport(dot1q-tunnel tag)------->vlanawarebrige-----(tag)--->vnet - - $vlanprotocol = "802.1q" if !$vlanprotocol; - my $svlan_iface = "sv_".$zoneid; - - # ovs dot1q-tunnel port - @iface_config = (); - push @iface_config, "ovs_type OVSIntPort"; - push @iface_config, "ovs_bridge $bridge"; - push @iface_config, "ovs_mtu $mtu" if $mtu; - push @iface_config, "ovs_options vlan_mode=dot1q-tunnel tag=$stag other_config:qinq-ethtype=$vlanprotocol"; - push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; - - # redefine main ovs bridge, ifupdown2 will merge ovs_ports - @{$config->{$bridge}}[0] = "ovs_ports" if !@{$config->{$bridge}}[0]; - my @ovs_ports = split / / , @{$config->{$bridge}}[0]; - @{$config->{$bridge}}[0] .= " $svlan_iface" if !grep( $_ eq $svlan_iface, @ovs_ports ); - - $zone_bridge_ports = $svlan_iface; - - } elsif ($vlan_aware) { - # VLAN_aware_brige-(tag)----->vlanwarebridge-(tag)----->vnet - - if ($vlanprotocol) { - @iface_config = (); - push @iface_config, "bridge-vlan-protocol $vlanprotocol"; - push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge}; - } - - $zone_bridge_ports = "$bridge.$stag"; - - } else { - # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet - - my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge); - - for my $bridge_iface (@bridge_ifaces) { - # use named vlan interface to avoid too long names - my $svlan_iface = "sv_$zoneid"; - - # svlan - @iface_config = (); - push @iface_config, "vlan-raw-device $bridge_iface"; - push @iface_config, "vlan-id $stag"; - push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol; - push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; - - $zone_bridge_ports = $svlan_iface; - last; - } - } - - # veth peer for notag vnet - @iface_config = (); - push @iface_config, "link-type veth"; - push @iface_config, "veth-peer-name $zone_notag_uplinkpeer"; - push(@{$config->{$zone_notag_uplink}}, @iface_config) if !$config->{$zone_notag_uplink}; - - @iface_config = (); - push @iface_config, "link-type veth"; - push @iface_config, "veth-peer-name $zone_notag_uplink"; - push(@{$config->{$zone_notag_uplinkpeer}}, @iface_config) if !$config->{$zone_notag_uplinkpeer}; - - # zone vlan aware bridge - @iface_config = (); - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "bridge-stp off"; - push @iface_config, "bridge-ports $zone_bridge_ports $zone_notag_uplink"; - push @iface_config, "bridge-fd 0"; - push @iface_config, "bridge-vlan-aware yes"; - push @iface_config, "bridge-vids 2-4094"; - push(@{$config->{$zone}}, @iface_config) if !$config->{$zone}; - - # vnet bridge - @iface_config = (); - push @iface_config, "bridge_ports $vnet_bridge_ports"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - if($vnet->{vlanaware}) { - push @iface_config, "bridge-vlan-aware yes"; - push @iface_config, "bridge-vids 2-4094"; - } - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "alias $vnet->{alias}" if $vnet->{alias}; - push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; -} - -sub status { - my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; - - my $bridge = $plugin_config->{bridge}; - my $err_msg = []; - - if (!-d "/sys/class/net/$bridge") { - push @$err_msg, "missing $bridge"; - return $err_msg; - } - - my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); - - my $tag = $vnet->{tag}; - my $vnet_uplink = "ln_".$vnetid; - my $vnet_uplinkpeer = "pr_".$vnetid; - my $zone_notag_uplink = "ln_".$zone; - my $zone_notag_uplinkpeer = "pr_".$zone; - my $zonebridge = "z_$zone"; - - # ifaces to check - my $ifaces = [ $vnetid, $bridge ]; - - push @$ifaces, $zonebridge; - push @$ifaces, $zone_notag_uplink; - push @$ifaces, $zone_notag_uplinkpeer; - - if (!$vlan_aware) { - my $svlan_iface = "sv_$zone"; - push @$ifaces, $svlan_iface; - } - - foreach my $iface (@{$ifaces}) { - if (!$status->{$iface}->{status}) { - push @$err_msg, "missing $iface"; - } elsif ($status->{$iface}->{status} ne 'pass') { - push @$err_msg, "error $iface"; - } - } - return $err_msg; -} - -sub vnet_update_hook { - my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; - - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - - my $tag = $vnet->{tag}; - raise_param_exc({ tag => "VLAN tag maximal value is 4096" }) if $tag && $tag > 4096; - - # verify that tag is not already defined in another vnet on same zone - for my $id (sort keys %{$vnet_cfg->{ids}}) { - next if $id eq $vnetid; - my $other_vnet = $vnet_cfg->{ids}->{$id}; - next if $vnet->{zone} ne $other_vnet->{zone}; - my $other_tag = $other_vnet->{tag}; - if ($tag) { - raise_param_exc({ tag => "tag $tag already exist in zone $vnet->{zone} vnet $id"}) - if $other_tag && $tag eq $other_tag; - } else { - raise_param_exc({ tag => "tag-less vnet already exists in zone $vnet->{zone} vnet $id"}) - if !$other_tag; - } - } -} - -1; - - diff --git a/PVE/Network/SDN/Zones/SimplePlugin.pm b/PVE/Network/SDN/Zones/SimplePlugin.pm deleted file mode 100644 index 7757747..0000000 --- a/PVE/Network/SDN/Zones/SimplePlugin.pm +++ /dev/null @@ -1,159 +0,0 @@ -package PVE::Network::SDN::Zones::SimplePlugin; - -use strict; -use warnings; -use PVE::Network::SDN::Zones::Plugin; -use PVE::Exception qw(raise raise_param_exc); -use PVE::Cluster; -use PVE::Tools; - -use base('PVE::Network::SDN::Zones::Plugin'); - -sub type { - return 'simple'; -} - -sub properties { - return { - dns => { - type => 'string', - description => "dns api server", - }, - reversedns => { - type => 'string', - description => "reverse dns api server", - }, - dnszone => { - type => 'string', format => 'dns-name', - description => "dns domain zone ex: mydomain.com", - } - }; -} - -sub options { - return { - nodes => { optional => 1}, - mtu => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; - - return $config if$config->{$vnetid}; # nothing to do - - my $mac = $vnet->{mac}; - my $alias = $vnet->{alias}; - my $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; - - # vnet bridge - my @iface_config = (); - - my $address = {}; - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); - - my $ipv4 = undef; - my $ipv6 = undef; - my $enable_forward_v4 = undef; - my $enable_forward_v6 = undef; - - foreach my $subnetid (sort keys %{$subnets}) { - my $subnet = $subnets->{$subnetid}; - my $cidr = $subnet->{cidr}; - my $mask = $subnet->{mask}; - - my $gateway = $subnet->{gateway}; - if ($gateway) { - push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway}); - $address->{$gateway} = 1; - } - - my $iptables = undef; - my $checkrouteip = undef; - my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4; - - if ( $ipversion == 6) { - $ipv6 = 1; - $iptables = "ip6tables"; - $checkrouteip = '2001:4860:4860::8888'; - $enable_forward_v6 = 1 if $gateway; - } else { - $ipv4 = 1; - $iptables = "iptables"; - $checkrouteip = '8.8.8.8'; - $enable_forward_v4 = 1 if $gateway; - } - - #add route for /32 pointtopoint - push @iface_config, "up ip route add $cidr dev $vnetid" if $mask == 32 && $ipversion == 4; - if ($subnet->{snat}) { - #find outgoing interface - my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip); - if ($outip && $outiface) { - #use snat, faster than masquerade - push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; - push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; - #add conntrack zone once on outgoing interface - push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1"; - push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1"; - } - } - } - - push @iface_config, "hwaddress $mac" if $mac; - push @iface_config, "bridge_ports none"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - if ($vnet->{vlanaware}) { - push @iface_config, "bridge-vlan-aware yes"; - push @iface_config, "bridge-vids 2-4094"; - } - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "alias $alias" if $alias; - push @iface_config, "ip-forward on" if $enable_forward_v4; - push @iface_config, "ip6-forward on" if $enable_forward_v6; - - push @{$config->{$vnetid}}, @iface_config; - - return $config; -} - -sub status { - my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; - - # ifaces to check - my $ifaces = [ $vnetid ]; - my $err_msg = []; - foreach my $iface (@{$ifaces}) { - if (!$status->{$iface}->{status}) { - push @$err_msg, "missing $iface"; - } elsif ($status->{$iface}->{status} ne 'pass') { - push @$err_msg, "error iface $iface"; - } - } - return $err_msg; -} - - -sub vnet_update_hook { - my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; - - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - my $tag = $vnet->{tag}; - - raise_param_exc({ tag => "vlan tag is not allowed on simple zone"}) if defined($tag); - - if (!defined($vnet->{mac})) { - my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); - $vnet->{mac} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); - } -} - -1; - - diff --git a/PVE/Network/SDN/Zones/VlanPlugin.pm b/PVE/Network/SDN/Zones/VlanPlugin.pm deleted file mode 100644 index 0bb6b8a..0000000 --- a/PVE/Network/SDN/Zones/VlanPlugin.pm +++ /dev/null @@ -1,199 +0,0 @@ -package PVE::Network::SDN::Zones::VlanPlugin; - -use strict; -use warnings; -use PVE::Network::SDN::Zones::Plugin; -use PVE::Exception qw(raise raise_param_exc); - -use base('PVE::Network::SDN::Zones::Plugin'); - -sub type { - return 'vlan'; -} - -PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange); -sub pve_verify_sdn_vlanrange { - my ($vlanstr) = @_; - - PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096'); - - return $vlanstr; -} - -sub properties { - return { - 'bridge' => { - type => 'string', - }, - 'bridge-disable-mac-learning' => { - type => 'boolean', - description => "Disable auto mac learning.", - } - }; -} - -sub options { - - return { - nodes => { optional => 1}, - 'bridge' => { optional => 0 }, - 'bridge-disable-mac-learning' => { optional => 1 }, - mtu => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; - - my $bridge = $plugin_config->{bridge}; - PVE::Network::SDN::Zones::Plugin::find_bridge($bridge); - - my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); - my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); - - my $tag = $vnet->{tag}; - my $alias = $vnet->{alias}; - my $mtu = $plugin_config->{mtu}; - - my $vnet_uplink = "ln_".$vnetid; - my $vnet_uplinkpeer = "pr_".$vnetid; - - my @iface_config = (); - - if($is_ovs) { - - # keep vmbrXvY for compatibility with existing network - # eth0----ovs vmbr0--(ovsintport tag)---->vnet---->vm - - @iface_config = (); - push @iface_config, "ovs_type OVSIntPort"; - push @iface_config, "ovs_bridge $bridge"; - push @iface_config, "ovs_mtu $mtu" if $mtu; - if($vnet->{vlanaware}) { - push @iface_config, "ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=$tag"; - } else { - push @iface_config, "ovs_options tag=$tag"; - } - push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink}; - - #redefine main ovs bridge, ifupdown2 will merge ovs_ports - @iface_config = (); - push @iface_config, "ovs_ports $vnet_uplink"; - push(@{$config->{$bridge}}, @iface_config); - - } elsif ($vlan_aware) { - # eth0----vlanaware bridge vmbr0--(vmbr0.X tag)---->vnet---->vm - $vnet_uplink = "$bridge.$tag"; - } else { - - # keep vmbrXvY for compatibility with existing network - # eth0<---->eth0.X----vmbr0v10------vnet---->vm - - my $bridgevlan = $bridge."v".$tag; - - my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge); - - my $bridge_ports = ""; - foreach my $bridge_iface (@bridge_ifaces) { - $bridge_ports .= " $bridge_iface.$tag"; - } - - @iface_config = (); - push @iface_config, "link-type veth"; - push @iface_config, "veth-peer-name $vnet_uplinkpeer"; - push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink}; - - @iface_config = (); - push @iface_config, "link-type veth"; - push @iface_config, "veth-peer-name $vnet_uplink"; - push(@{$config->{$vnet_uplinkpeer}}, @iface_config) if !$config->{$vnet_uplinkpeer}; - - @iface_config = (); - push @iface_config, "bridge_ports $bridge_ports $vnet_uplinkpeer"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - push(@{$config->{$bridgevlan}}, @iface_config) if !$config->{$bridgevlan}; - } - - #vnet bridge - @iface_config = (); - push @iface_config, "bridge_ports $vnet_uplink"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - if($vnet->{vlanaware}) { - push @iface_config, "bridge-vlan-aware yes"; - push @iface_config, "bridge-vids 2-4094"; - } - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "alias $alias" if $alias; - push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; - - return $config; -} - -sub status { - my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; - - my $bridge = $plugin_config->{bridge}; - - my $err_msg = []; - if (!-d "/sys/class/net/$bridge") { - push @$err_msg, "missing $bridge"; - return $err_msg; - } - - my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); - my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); - - my $tag = $vnet->{tag}; - my $vnet_uplink = "ln_".$vnetid; - my $vnet_uplinkpeer = "pr_".$vnetid; - - # ifaces to check - my $ifaces = [ $vnetid, $bridge ]; - if($is_ovs) { - push @$ifaces, $vnet_uplink; - } elsif (!$vlan_aware) { - my $bridgevlan = $bridge."v".$tag; - push @$ifaces, $bridgevlan; - push @$ifaces, $vnet_uplink; - push @$ifaces, $vnet_uplinkpeer; - } - - foreach my $iface (@{$ifaces}) { - if (!$status->{$iface}->{status}) { - push @$err_msg, "missing $iface"; - } elsif ($status->{$iface}->{status} ne 'pass') { - push @$err_msg, "error iface $iface"; - } - } - return $err_msg; -} - -sub vnet_update_hook { - my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; - - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - my $tag = $vnet->{tag}; - - raise_param_exc({ tag => "missing vlan tag"}) if !defined($vnet->{tag}); - raise_param_exc({ tag => "vlan tag max value is 4096"}) if $vnet->{tag} > 4096; - - # verify that tag is not already defined in another vnet on same zone - foreach my $id (keys %{$vnet_cfg->{ids}}) { - next if $id eq $vnetid; - my $othervnet = $vnet_cfg->{ids}->{$id}; - my $other_tag = $othervnet->{tag}; - next if $vnet->{zone} ne $othervnet->{zone}; - raise_param_exc({ tag => "tag $tag already exist in vnet $id"}) if $other_tag && $tag eq $other_tag; - } -} - -1; - - diff --git a/PVE/Network/SDN/Zones/VxlanPlugin.pm b/PVE/Network/SDN/Zones/VxlanPlugin.pm deleted file mode 100644 index c523cf7..0000000 --- a/PVE/Network/SDN/Zones/VxlanPlugin.pm +++ /dev/null @@ -1,118 +0,0 @@ -package PVE::Network::SDN::Zones::VxlanPlugin; - -use strict; -use warnings; -use PVE::Network::SDN::Zones::Plugin; -use PVE::Tools qw($IPV4RE); -use PVE::INotify; -use PVE::Network::SDN::Controllers::EvpnPlugin; -use PVE::Exception qw(raise raise_param_exc); - -use base('PVE::Network::SDN::Zones::Plugin'); - -PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange); -sub pve_verify_sdn_vxlanrange { - my ($vxlanstr) = @_; - - PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216'); - - return $vxlanstr; -} - -sub type { - return 'vxlan'; -} - -sub properties { - return { - 'peers' => { - description => "peers address list.", - type => 'string', format => 'ip-list' - }, - }; -} - -sub options { - return { - nodes => { optional => 1}, - peers => { optional => 0 }, - mtu => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - }; -} - -# Plugin implementation -sub generate_sdn_config { - my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; - - my $tag = $vnet->{tag}; - my $alias = $vnet->{alias}; - my $multicastaddress = $plugin_config->{'multicast-address'}; - my @peers; - @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; - my $vxlan_iface = "vxlan_$vnetid"; - - die "missing vxlan tag" if !$tag; - - my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers); - - my $mtu = 1450; - $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu}; - $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; - - #vxlan interface - my @iface_config = (); - push @iface_config, "vxlan-id $tag"; - - for my $address (@peers) { - next if $address eq $ifaceip; - push @iface_config, "vxlan_remoteip $address"; - } - - - push @iface_config, "mtu $mtu" if $mtu; - push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface}; - - #vnet bridge - @iface_config = (); - push @iface_config, "bridge_ports $vxlan_iface"; - push @iface_config, "bridge_stp off"; - push @iface_config, "bridge_fd 0"; - if ($vnet->{vlanaware}) { - push @iface_config, "bridge-vlan-aware yes"; - push @iface_config, "bridge-vids 2-4094"; - } - push @iface_config, "mtu $mtu" if $mtu; - push @iface_config, "alias $alias" if $alias; - push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; - - return $config; -} - -sub vnet_update_hook { - my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; - - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - my $tag = $vnet->{tag}; - - raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag); - raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216; - - # verify that tag is not already defined globally (vxlan-id are unique) - for my $id (sort keys %{$vnet_cfg->{ids}}) { - next if $id eq $vnetid; - my $othervnet = $vnet_cfg->{ids}->{$id}; - my $other_tag = $othervnet->{tag}; - my $other_zoneid = $othervnet->{zone}; - my $other_zone = $zone_cfg->{ids}->{$other_zoneid}; - next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn'; - raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag; - } -} - -1; - - diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..1529c87 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,15 @@ +all: + $(MAKE) -C PVE + +.PHONY: clean +clean: + $(MAKE) -C test $@ + $(MAKE) -C PVE $@ + +.PHONY: test +test: + $(MAKE) -C $@ + +.PHONY: install +install: + $(MAKE) -C PVE $@ diff --git a/src/PVE/API2/Makefile b/src/PVE/API2/Makefile new file mode 100644 index 0000000..28b2830 --- /dev/null +++ b/src/PVE/API2/Makefile @@ -0,0 +1,4 @@ + +.PHONY: install +install: + make -C Network install diff --git a/src/PVE/API2/Network/Makefile b/src/PVE/API2/Network/Makefile new file mode 100644 index 0000000..396f79d --- /dev/null +++ b/src/PVE/API2/Network/Makefile @@ -0,0 +1,9 @@ +SOURCES=SDN.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/$$i; done + make -C SDN install diff --git a/src/PVE/API2/Network/SDN.pm b/src/PVE/API2/Network/SDN.pm new file mode 100644 index 0000000..f129d60 --- /dev/null +++ b/src/PVE/API2/Network/SDN.pm @@ -0,0 +1,144 @@ +package PVE::API2::Network::SDN; + +use strict; +use warnings; + +use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file); +use PVE::Exception qw(raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RESTHandler; +use PVE::RPCEnvironment; +use PVE::SafeSyslog; +use PVE::Tools qw(run_command); +use PVE::Network::SDN; + +use PVE::API2::Network::SDN::Controllers; +use PVE::API2::Network::SDN::Vnets; +use PVE::API2::Network::SDN::Zones; +use PVE::API2::Network::SDN::Ipams; +use PVE::API2::Network::SDN::Dns; + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Vnets", + path => 'vnets', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Zones", + path => 'zones', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Controllers", + path => 'controllers', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Ipams", + path => 'ipams', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Dns", + path => 'dns', +}); + +__PACKAGE__->register_method({ + name => 'index', + path => '', + method => 'GET', + description => "Directory index.", + permissions => { + check => ['perm', '/', [ 'SDN.Audit' ]], + }, + parameters => { + additionalProperties => 0, + properties => {}, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + id => { type => 'string' }, + }, + }, + links => [ { rel => 'child', href => "{id}" } ], + }, + code => sub { + my ($param) = @_; + + my $res = [ + { id => 'vnets' }, + { id => 'zones' }, + { id => 'controllers' }, + { id => 'ipams' }, + { id => 'dns' }, + ]; + + return $res; + }}); + +my $create_reload_network_worker = sub { + my ($nodename) = @_; + + # FIXME: how to proxy to final node ? + my $upid; + run_command(['pvesh', 'set', "/nodes/$nodename/network"], outfunc => sub { + my $line = shift; + if ($line =~ /^["']?(UPID:[^\s"']+)["']?$/) { + $upid = $1; + } + }); + #my $upid = PVE::API2::Network->reload_network_config(node => $nodename}); + my $res = PVE::Tools::upid_decode($upid); + + return $res->{pid}; +}; + +__PACKAGE__->register_method ({ + name => 'reload', + protected => 1, + path => '', + method => 'PUT', + description => "Apply sdn controller changes && reload.", + permissions => { + check => ['perm', '/sdn', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + }, + returns => { + type => 'string', + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + PVE::Network::SDN::commit_config(); + + my $code = sub { + $rpcenv->{type} = 'priv'; # to start tasks in background + PVE::Cluster::check_cfs_quorum(); + my $nodelist = PVE::Cluster::get_nodelist(); + for my $node (@$nodelist) { + my $pid = eval { $create_reload_network_worker->($node) }; + warn $@ if $@; + } + + # FIXME: use libpve-apiclient (like in cluster join) to create + # tasks and moitor the tasks. + + return; + }; + + return $rpcenv->fork_worker('reloadnetworkall', undef, $authuser, $code); + + }}); + + +1; diff --git a/src/PVE/API2/Network/SDN/Controllers.pm b/src/PVE/API2/Network/SDN/Controllers.pm new file mode 100644 index 0000000..d8f18ab --- /dev/null +++ b/src/PVE/API2/Network/SDN/Controllers.pm @@ -0,0 +1,290 @@ +package PVE::API2::Network::SDN::Controllers; + +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::Zones; +use PVE::Network::SDN::Controllers; +use PVE::Network::SDN::Controllers::Plugin; +use PVE::Network::SDN::Controllers::EvpnPlugin; +use PVE::Network::SDN::Controllers::BgpPlugin; +use PVE::Network::SDN::Controllers::FaucetPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $sdn_controllers_type_enum = PVE::Network::SDN::Controllers::Plugin->lookup_types(); + +my $api_sdn_controllers_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id)); + $scfg->{controller} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN controllers index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/controllers/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + type => { + description => "Only list sdn controllers of specific type", + type => 'string', + enum => $sdn_controllers_type_enum, + optional => 1, + }, + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { controller => { type => 'string' }, + type => { type => 'string' }, + state => { type => 'string', optional => 1 }, + pending => { optional => 1}, + }, + }, + links => [ { rel => 'child', href => "{controller}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Controllers::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{controllers}; + } else { + $cfg = PVE::Network::SDN::Controllers::config(); + } + + my @sids = PVE::Network::SDN::Controllers::sdn_controllers_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/controllers/$id", $privs, 1); + + my $scfg = &$api_sdn_controllers_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{controller}', + method => 'GET', + description => "Read sdn controller configuration.", + permissions => { + check => ['perm', '/sdn/controllers/{controller}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + controller => get_standard_option('pve-sdn-controller-id'), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Controllers::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{controllers}; + } else { + $cfg = PVE::Network::SDN::Controllers::config(); + } + + return &$api_sdn_controllers_config($cfg, $param->{controller}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn controller object.", + permissions => { + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'controller'); + + my $plugin = PVE::Network::SDN::Controllers::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 $controller_cfg = PVE::Network::SDN::Controllers::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id, 1)) { + die "sdn controller object ID '$id' already defined\n"; + } + + $controller_cfg->{ids}->{$id} = $opts; + $plugin->on_update_hook($id, $controller_cfg); + + PVE::Network::SDN::Controllers::write_config($controller_cfg); + + }, "create sdn controller object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{controller}', + method => 'PUT', + description => "Update sdn controller object configuration.", + permissions => { + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'controller'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + + PVE::SectionConfig::assert_if_modified($controller_cfg, $digest); + + my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id); + + my $plugin = PVE::Network::SDN::Controllers::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($id, $controller_cfg); + + PVE::Network::SDN::Controllers::write_config($controller_cfg); + + + }, "update sdn controller object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{controller}', + method => 'DELETE', + description => "Delete sdn controller object configuration.", + permissions => { + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + controller => get_standard_option('pve-sdn-controller-id', { + completion => \&PVE::Network::SDN::Controllers::complete_sdn_controllers, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'controller'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Controllers::config(); + + my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + + $plugin->on_delete_hook($id, $zone_cfg); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Controllers::write_config($cfg); + + }, "delete sdn controller object failed"); + + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Dns.pm b/src/PVE/API2/Network/SDN/Dns.pm new file mode 100644 index 0000000..3d08552 --- /dev/null +++ b/src/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/'", + 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($opts->{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/src/PVE/API2/Network/SDN/Ipams.pm b/src/PVE/API2/Network/SDN/Ipams.pm new file mode 100644 index 0000000..6410e8e --- /dev/null +++ b/src/PVE/API2/Network/SDN/Ipams.pm @@ -0,0 +1,248 @@ +package PVE::API2::Network::SDN::Ipams; + +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::Ipams; +use PVE::Network::SDN::Ipams::Plugin; +use PVE::Network::SDN::Ipams::PVEPlugin; +use PVE::Network::SDN::Ipams::PhpIpamPlugin; +use PVE::Network::SDN::Ipams::NetboxPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $sdn_ipams_type_enum = PVE::Network::SDN::Ipams::Plugin->lookup_types(); + +my $api_sdn_ipams_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id)); + $scfg->{ipam} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN ipams index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/ipams/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + type => { + description => "Only list sdn ipams of specific type", + type => 'string', + enum => $sdn_ipams_type_enum, + optional => 1, + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { ipam => { type => 'string'}, + type => { type => 'string'}, + }, + }, + links => [ { rel => 'child', href => "{ipam}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + + my $cfg = PVE::Network::SDN::Ipams::config(); + + my @sids = PVE::Network::SDN::Ipams::sdn_ipams_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/ipams/$id", $privs, 1); + + my $scfg = &$api_sdn_ipams_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{ipam}', + method => 'GET', + description => "Read sdn ipam configuration.", + permissions => { + check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + ipam => get_standard_option('pve-sdn-ipam-id'), + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = PVE::Network::SDN::Ipams::config(); + + return &$api_sdn_ipams_config($cfg, $param->{ipam}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn ipam object.", + permissions => { + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'ipam'); + + my $plugin = PVE::Network::SDN::Ipams::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 $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id, 1)) { + die "sdn ipam object ID '$id' already defined\n"; + } + + $ipam_cfg->{ids}->{$id} = $opts; + + my $plugin_config = $opts; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $plugin->on_update_hook($plugin_config); + + PVE::Network::SDN::Ipams::write_config($ipam_cfg); + + }, "create sdn ipam object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{ipam}', + method => 'PUT', + description => "Update sdn ipam object configuration.", + permissions => { + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'ipam'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + + PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest); + + my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id); + + my $plugin = PVE::Network::SDN::Ipams::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::Ipams::write_config($ipam_cfg); + + }, "update sdn ipam object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{ipam}', + method => 'DELETE', + description => "Delete sdn ipam object configuration.", + permissions => { + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + ipam => get_standard_option('pve-sdn-ipam-id', { + completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'ipam'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Ipams::config(); + + my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); + + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Ipams::write_config($cfg); + + }, "delete sdn zone object failed"); + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Makefile b/src/PVE/API2/Network/SDN/Makefile new file mode 100644 index 0000000..3683fa4 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Makefile @@ -0,0 +1,10 @@ +SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/$$i; done + make -C Zones install + diff --git a/src/PVE/API2/Network/SDN/Subnets.pm b/src/PVE/API2/Network/SDN/Subnets.pm new file mode 100644 index 0000000..377a568 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Subnets.pm @@ -0,0 +1,303 @@ +package PVE::API2::Network::SDN::Subnets; + +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::Exception qw(raise raise_param_exc); +use PVE::Network::SDN; +use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::SubnetPlugin; +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Ipams; +use PVE::Network::SDN::Ipams::Plugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $api_sdn_subnets_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id)); + $scfg->{subnet} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN subnets index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/subnets/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [ { rel => 'child', href => "{subnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $vnetid = $param->{vnet}; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Subnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{subnets}; + } else { + $cfg = PVE::Network::SDN::Subnets::config(); + } + + my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid/subnets/$id", $privs, 1); + + my $scfg = &$api_sdn_subnets_config($cfg, $id); + next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid; + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{subnet}', + method => 'GET', + description => "Read sdn subnet configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets/{subnet}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + subnet => get_standard_option('pve-sdn-subnet-id', { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Subnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{subnets}; + } else { + $cfg = PVE::Network::SDN::Subnets::config(); + } + + my $scfg = &$api_sdn_subnets_config($cfg, $param->{subnet}); + + raise_param_exc({ vnet => "wrong vnet"}) if $param->{vnet} ne $scfg->{vnet}; + + return $scfg; + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn subnet object.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::SubnetPlugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $cidr = extract_param($param, 'subnet'); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn") if ! -d '/etc/pve/sdn'; + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + my $vnet = $param->{vnet}; + my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; + my $id = $cidr =~ s/\//-/r; + $id = "$zoneid-$id"; + + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) { + die "sdn subnet object ID '$id' already defined\n"; + } + + $cfg->{ids}->{$id} = $opts; + + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); + PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet); + + PVE::Network::SDN::Subnets::write_config($cfg); + + }, "create sdn subnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{subnet}', + method => 'PUT', + description => "Update sdn subnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'subnet'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + my $vnet = $param->{vnet}; + my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; + + my $scfg = &$api_sdn_subnets_config($cfg, $id); + + PVE::SectionConfig::assert_if_modified($cfg, $digest); + + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1); + $cfg->{ids}->{$id} = $opts; + + raise_param_exc({ ipam => "you can't change ipam"}) if $opts->{ipam} && $scfg->{ipam} && $opts->{ipam} ne $scfg->{ipam}; + + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); + PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg); + + PVE::Network::SDN::Subnets::write_config($cfg); + + }, "update sdn subnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{subnet}', + method => 'DELETE', + description => "Delete sdn subnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + subnet => get_standard_option('pve-sdn-subnet-id', { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'subnet'); + + PVE::Network::SDN::lock_sdn_config( + sub { + my $cfg = PVE::Network::SDN::Subnets::config(); + + my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1); + + my $vnets_cfg = PVE::Network::SDN::Vnets::config(); + + PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg); + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet = $param->{vnet}; + my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; + + PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg); + + delete $cfg->{ids}->{$id}; + + PVE::Network::SDN::Subnets::write_config($cfg); + + }, "delete sdn subnet object failed"); + + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Vnets.pm b/src/PVE/API2/Network/SDN/Vnets.pm new file mode 100644 index 0000000..811a2e8 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Vnets.pm @@ -0,0 +1,292 @@ +package PVE::API2::Network::SDN::Vnets; + +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::Zones; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN::VnetPlugin; +use PVE::Network::SDN::Subnets; +use PVE::API2::Network::SDN::Subnets; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; +use PVE::Exception qw(raise raise_param_exc); + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Subnets", + path => '{vnet}/subnets', +}); + +my $api_sdn_vnets_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id)); + $scfg->{vnet} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +my $api_sdn_vnets_deleted_config = sub { + my ($cfg, $running_cfg, $id) = @_; + + if (!$cfg->{ids}->{$id}) { + + my $vnet_cfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($running_cfg->{vnets}, $id)); + $vnet_cfg->{state} = "deleted"; + $vnet_cfg->{vnet} = $id; + return $vnet_cfg; + } +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN vnets index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'" + ." permissions on '/sdn/vnets/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [ { rel => 'child', href => "{vnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Vnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{vnets}; + } else { + $cfg = PVE::Network::SDN::Vnets::config(); + } + + my @sids = PVE::Network::SDN::Vnets::sdn_vnets_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/vnets/$id", $privs, 1); + + my $scfg = &$api_sdn_vnets_config($cfg, $id); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{vnet}', + method => 'GET', + description => "Read sdn vnet configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id', { + completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, + }), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Vnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{vnets}; + } else { + $cfg = PVE::Network::SDN::Vnets::config(); + } + + return $api_sdn_vnets_config->($cfg, $param->{vnet}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn vnet object.", + permissions => { + check => ['perm', '/sdn/vnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::VnetPlugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'vnet'); + + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Vnets::config(); + my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 1, 1); + + if (PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id, 1)) { + die "sdn vnet object ID '$id' already defined\n"; + } + $cfg->{ids}->{$id} = $opts; + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $zoneid = $cfg->{ids}->{$id}->{zone}; + my $plugin_config = $zone_cfg->{ids}->{$zoneid}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->vnet_update_hook($cfg, $id, $zone_cfg); + + PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); + + PVE::Network::SDN::Vnets::write_config($cfg); + + }, "create sdn vnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{vnet}', + method => 'PUT', + description => "Update sdn vnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::VnetPlugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'vnet'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Vnets::config(); + + PVE::SectionConfig::assert_if_modified($cfg, $digest); + + + my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1); + raise_param_exc({ zone => "missing zone"}) if !$opts->{zone}; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($id); + raise_param_exc({ zone => "can't change zone if subnets exists"}) if($subnets && $opts->{zone} ne $cfg->{ids}->{$id}->{zone}); + + $cfg->{ids}->{$id} = $opts; + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $zoneid = $cfg->{ids}->{$id}->{zone}; + my $plugin_config = $zone_cfg->{ids}->{$zoneid}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->vnet_update_hook($cfg, $id, $zone_cfg); + + PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); + + PVE::Network::SDN::Vnets::write_config($cfg); + + }, "update sdn vnet object failed"); + + return undef; + } +}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{vnet}', + method => 'DELETE', + description => "Delete sdn vnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id', { + completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'vnet'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Vnets::config(); + my $scfg = PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id); # check if exists + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + + PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Vnets::write_config($cfg); + + }, "delete sdn vnet object failed"); + + + return undef; + } +}); + +1; diff --git a/src/PVE/API2/Network/SDN/Zones.pm b/src/PVE/API2/Network/SDN/Zones.pm new file mode 100644 index 0000000..6e53240 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones.pm @@ -0,0 +1,351 @@ +package PVE::API2::Network::SDN::Zones; + +use strict; +use warnings; + +use Storable qw(dclone); + +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Exception qw(raise raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); + +use PVE::Network::SDN::Dns; +use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN; + +use PVE::Network::SDN::Zones::EvpnPlugin; +use PVE::Network::SDN::Zones::FaucetPlugin; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Network::SDN::Zones::QinQPlugin; +use PVE::Network::SDN::Zones::SimplePlugin; +use PVE::Network::SDN::Zones::VlanPlugin; +use PVE::Network::SDN::Zones::VxlanPlugin; +use PVE::Network::SDN::Zones; + +use PVE::RESTHandler; +use base qw(PVE::RESTHandler); + +my $sdn_zones_type_enum = PVE::Network::SDN::Zones::Plugin->lookup_types(); + +my $api_sdn_zones_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id)); + $scfg->{zone} = $id; + $scfg->{digest} = $cfg->{digest}; + + if ($scfg->{nodes}) { + $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $scfg->{nodes}); + } + + if ($scfg->{exitnodes}) { + $scfg->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $scfg->{exitnodes}); + } + + my $pending = $scfg->{pending}; + if ($pending->{nodes}) { + $pending->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $pending->{nodes}); + } + + if ($pending->{exitnodes}) { + $pending->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes}); + } + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN zones index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + type => { + description => "Only list SDN zones of specific type", + type => 'string', + enum => $sdn_zones_type_enum, + optional => 1, + }, + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { zone => { type => 'string'}, + type => { type => 'string'}, + mtu => { type => 'integer', optional => 1 }, + dns => { type => 'string', optional => 1}, + reversedns => { type => 'string', optional => 1}, + dnszone => { type => 'string', optional => 1}, + ipam => { type => 'string', optional => 1}, + pending => { optional => 1}, + state => { type => 'string', optional => 1}, + nodes => { type => 'string', optional => 1}, + }, + }, + links => [ { rel => 'child', href => "{zone}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $cfg = {}; + if ($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Zones::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{zones}; + } else { + $cfg = PVE::Network::SDN::Zones::config(); + } + + my @sids = PVE::Network::SDN::Zones::sdn_zones_ids($cfg); + my $res = []; + for my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/zones/$id", $privs, 1); + + my $scfg = &$api_sdn_zones_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{zone}', + method => 'GET', + description => "Read sdn zone configuration.", + permissions => { + check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + } + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if ($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Zones::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{zones}; + } else { + $cfg = PVE::Network::SDN::Zones::config(); + } + + return &$api_sdn_zones_config($cfg, $param->{zone}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn zone object.", + permissions => { + check => ['perm', '/sdn/zones', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Zones::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'zone'); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); + + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config(sub { + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id, 1)) { + die "sdn zone object ID '$id' already defined\n"; + } + + my $dnsserver = $opts->{dns}; + raise_param_exc({ dns => "$dnsserver don't exist"}) + if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; + + my $reversednsserver = $opts->{reversedns}; + raise_param_exc({ reversedns => "$reversednsserver don't exist"}) + if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver}; + + my $dnszone = $opts->{dnszone}; + raise_param_exc({ dnszone => "missing dns server"}) + if $dnszone && !$dnsserver; + + my $ipam = $opts->{ipam}; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam}; + + $zone_cfg->{ids}->{$id} = $opts; + $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); + + PVE::Network::SDN::Zones::write_config($zone_cfg); + + }, "create sdn zone object failed"); + + return; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{zone}', + method => 'PUT', + description => "Update sdn zone object configuration.", + permissions => { + check => ['perm', '/sdn/zones', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'zone'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + PVE::SectionConfig::assert_if_modified($zone_cfg, $digest); + + my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); + + if ($opts->{ipam} && !$scfg->{ipam} || $opts->{ipam} ne $scfg->{ipam}) { + + # don't allow ipam change if subnet are defined for now, need to implement resync ipam content + my $subnets_cfg = PVE::Network::SDN::Subnets::config(); + for my $subnetid (sort keys %{$subnets_cfg->{ids}}) { + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); + raise_param_exc({ ipam => "can't change ipam if a subnet is already defined in this zone"}) + if $subnet->{zone} eq $id; + } + } + + $zone_cfg->{ids}->{$id} = $opts; + + my $dnsserver = $opts->{dns}; + raise_param_exc({ dns => "$dnsserver don't exist"}) if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; + + my $reversednsserver = $opts->{reversedns}; + raise_param_exc({ reversedns => "$reversednsserver don't exist"}) if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver}; + + my $dnszone = $opts->{dnszone}; + raise_param_exc({ dnszone => "missing dns server"}) if $dnszone && !$dnsserver; + + my $ipam = $opts->{ipam}; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam}; + + $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); + + PVE::Network::SDN::Zones::write_config($zone_cfg); + + }, "update sdn zone object failed"); + + return; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{zone}', + method => 'DELETE', + description => "Delete sdn zone object configuration.", + permissions => { + check => ['perm', '/sdn/zones', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id', { + completion => \&PVE::Network::SDN::Zones::complete_sdn_zones, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'zone'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Zones::config(); + my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + + $plugin->on_delete_hook($id, $vnet_cfg); + + delete $cfg->{ids}->{$id}; + + PVE::Network::SDN::Zones::write_config($cfg); + }, "delete sdn zone object failed"); + + return; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Zones/Content.pm b/src/PVE/API2/Network/SDN/Zones/Content.pm new file mode 100644 index 0000000..66f49df --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones/Content.pm @@ -0,0 +1,85 @@ +package PVE::API2::Network::SDN::Zones::Content; + +use strict; +use warnings; +use Data::Dumper; + +use PVE::SafeSyslog; +use PVE::Cluster; +use PVE::INotify; +use PVE::Exception qw(raise_param_exc); +use PVE::RPCEnvironment; +use PVE::RESTHandler; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Network::SDN; + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "List zone content.", + permissions => { + check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], + }, + protected => 1, + proxyto => 'node', + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + zone => get_standard_option('pve-sdn-zone-id', { + completion => \&PVE::Network::SDN::Zones::complete_sdn_zone, + }), + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + vnet => { + description => "Vnet identifier.", + type => 'string', + }, + status => { + description => "Status.", + type => 'string', + optional => 1, + }, + statusmsg => { + description => "Status details", + type => 'string', + optional => 1, + }, + }, + }, + links => [ { rel => 'child', href => "{vnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + + my $authuser = $rpcenv->get_user(); + + my $zoneid = $param->{zone}; + + my $res = []; + + my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); + + foreach my $id (keys %{$vnet_status}) { + if ($vnet_status->{$id}->{zone} eq $zoneid) { + my $item->{vnet} = $id; + $item->{status} = $vnet_status->{$id}->{'status'}; + $item->{statusmsg} = $vnet_status->{$id}->{'statusmsg'}; + push @$res,$item; + } + } + + return $res; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Zones/Makefile b/src/PVE/API2/Network/SDN/Zones/Makefile new file mode 100644 index 0000000..9b0a42b --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones/Makefile @@ -0,0 +1,8 @@ +SOURCES=Status.pm Content.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/Zones/$$i; done diff --git a/src/PVE/API2/Network/SDN/Zones/Status.pm b/src/PVE/API2/Network/SDN/Zones/Status.pm new file mode 100644 index 0000000..17de68f --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones/Status.pm @@ -0,0 +1,111 @@ +package PVE::API2::Network::SDN::Zones::Status; + +use strict; +use warnings; + +use File::Path; +use File::Basename; +use PVE::Tools; +use PVE::INotify; +use PVE::Cluster; +use PVE::API2::Network::SDN::Zones::Content; +use PVE::RESTHandler; +use PVE::RPCEnvironment; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Exception qw(raise_param_exc); + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Zones::Content", + path => '{zone}/content', +}); + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "Get status for all zones.", + permissions => { + description => "Only list entries where you have 'SDN.Audit'", + user => 'all', + }, + protected => 1, + proxyto => 'node', + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node') + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + status => { + description => "Status of zone", + type => 'string', + enum => ['available', 'pending', 'error'], + }, + }, + }, + links => [ { rel => 'child', href => "{zone}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $localnode = PVE::INotify::nodename(); + + my $res = []; + + my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); + + foreach my $id (sort keys %{$zone_status}) { + my $item->{zone} = $id; + $item->{status} = $zone_status->{$id}->{'status'}; + push @$res, $item; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'diridx', + path => '{zone}', + method => 'GET', + description => "", + permissions => { + check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + zone => get_standard_option('pve-sdn-zone-id'), + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + subdir => { type => 'string' }, + }, + }, + links => [ { rel => 'child', href => "{subdir}" } ], + }, + code => sub { + my ($param) = @_; + my $res = [ + { subdir => 'content' }, + ]; + + return $res; + }}); + +1; diff --git a/src/PVE/Makefile b/src/PVE/Makefile new file mode 100644 index 0000000..7f1cf98 --- /dev/null +++ b/src/PVE/Makefile @@ -0,0 +1,8 @@ +all: + +.PHONY: install +install: + make -C Network install + make -C API2 install + +clean: diff --git a/src/PVE/Network/Makefile b/src/PVE/Network/Makefile new file mode 100644 index 0000000..277e19c --- /dev/null +++ b/src/PVE/Network/Makefile @@ -0,0 +1,9 @@ +SOURCES=SDN.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/$$i; done + make -C SDN install diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm new file mode 100644 index 0000000..b95dd5b --- /dev/null +++ b/src/PVE/Network/SDN.pm @@ -0,0 +1,283 @@ +package PVE::Network::SDN; + +use strict; +use warnings; + +use Data::Dumper; +use JSON; + +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use PVE::Network::SDN::Subnets; + +use PVE::Tools qw(extract_param dir_glob_regex run_command); +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); + + +my $running_cfg = "sdn/.running-config"; + +my $parse_running_cfg = sub { + my ($filename, $raw) = @_; + + my $cfg = {}; + + return $cfg if !defined($raw) || $raw eq ''; + + eval { + $cfg = from_json($raw); + }; + return {} if $@; + + return $cfg; +}; + +my $write_running_cfg = sub { + my ($filename, $cfg) = @_; + + my $json = to_json($cfg); + + return $json; +}; + +PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running_cfg); + + +# improve me : move status code inside plugins ? + +sub ifquery_check { + + my $cmd = ['ifquery', '-a', '-c', '-o','json']; + + my $result = ''; + my $reader = sub { $result .= shift }; + + eval { + run_command($cmd, outfunc => $reader); + }; + + my $resultjson = decode_json($result); + my $interfaces = {}; + + foreach my $interface (@$resultjson) { + my $name = $interface->{name}; + $interfaces->{$name} = { + status => $interface->{status}, + config => $interface->{config}, + config_status => $interface->{config_status}, + }; + } + + return $interfaces; +} + +sub status { + + my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status(); + return($zone_status, $vnet_status); +} + +sub running_config { + return cfs_read_file($running_cfg); +} + +sub pending_config { + my ($running_cfg, $cfg, $type) = @_; + + my $pending = {}; + + my $running_objects = $running_cfg->{$type}->{ids}; + my $config_objects = $cfg->{ids}; + + foreach my $id (sort keys %{$running_objects}) { + my $running_object = $running_objects->{$id}; + my $config_object = $config_objects->{$id}; + foreach my $key (sort keys %{$running_object}) { + $pending->{$id}->{$key} = $running_object->{$key}; + if(!keys %{$config_object}) { + $pending->{$id}->{state} = "deleted"; + } elsif (!defined($config_object->{$key})) { + $pending->{$id}->{"pending"}->{$key} = 'deleted'; + $pending->{$id}->{state} = "changed"; + } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) + ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) { + $pending->{$id}->{state} = "changed"; + } + } + $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"}); + } + + foreach my $id (sort keys %{$config_objects}) { + my $running_object = $running_objects->{$id}; + my $config_object = $config_objects->{$id}; + + foreach my $key (sort keys %{$config_object}) { + my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key}; + my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key}; + if($key eq 'type' || $key eq 'vnet') { + $pending->{$id}->{$key} = $config_value; + } else { + $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value); + } + if(!keys %{$running_object}) { + $pending->{$id}->{state} = "new"; + } elsif (!defined($running_value) && defined($config_value)) { + $pending->{$id}->{state} = "changed"; + } + } + $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"}); + } + + return {ids => $pending}; + +} + +sub commit_config { + + my $cfg = cfs_read_file($running_cfg); + my $version = $cfg->{version}; + + if ($version) { + $version++; + } else { + $version = 1; + } + + my $vnets_cfg = PVE::Network::SDN::Vnets::config(); + my $zones_cfg = PVE::Network::SDN::Zones::config(); + my $controllers_cfg = PVE::Network::SDN::Controllers::config(); + my $subnets_cfg = PVE::Network::SDN::Subnets::config(); + + my $vnets = { ids => $vnets_cfg->{ids} }; + my $zones = { ids => $zones_cfg->{ids} }; + my $controllers = { ids => $controllers_cfg->{ids} }; + my $subnets = { ids => $subnets_cfg->{ids} }; + + $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets }; + + cfs_write_file($running_cfg, $cfg); +} + +sub lock_sdn_config { + my ($code, $errmsg) = @_; + + cfs_lock_file($running_cfg, undef, $code); + + if (my $err = $@) { + $errmsg ? die "$errmsg: $err" : die $err; + } +} + +sub get_local_vnets { + + my $rpcenv = PVE::RPCEnvironment::get(); + + my $authuser = $rpcenv->get_user(); + + my $nodename = PVE::INotify::nodename(); + + my $cfg = PVE::Network::SDN::running_config(); + my $vnets_cfg = $cfg->{vnets}; + my $zones_cfg = $cfg->{zones}; + + my @vnetids = PVE::Network::SDN::Vnets::sdn_vnets_ids($vnets_cfg); + + my $vnets = {}; + + foreach my $vnetid (@vnetids) { + + my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid); + my $zoneid = $vnet->{zone}; + my $comments = $vnet->{alias}; + + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + + next if !$zoneid; + next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid", $privs, 1) && !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid", $privs, 1); + + my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid); + + next if defined($zone_config->{nodes}) && !$zone_config->{nodes}->{$nodename}; + my $ipam = $zone_config->{ipam} ? 1 : 0; + my $vlanaware = $vnet->{vlanaware} ? 1 : 0; + $vnets->{$vnetid} = { type => 'vnet', active => '1', ipam => $ipam, vlanaware => $vlanaware, comments => $comments }; + } + + return $vnets; +} + +sub generate_zone_config { + my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config(); + PVE::Network::SDN::Zones::write_etc_network_config($raw_config); +} + +sub generate_controller_config { + my ($reload) = @_; + + my $raw_config = PVE::Network::SDN::Controllers::generate_controller_config(); + PVE::Network::SDN::Controllers::write_controller_config($raw_config); + + PVE::Network::SDN::Controllers::reload_controller() if $reload; +} + +sub encode_value { + my ($type, $key, $value) = @_; + + if ($key eq 'nodes' || $key eq 'exitnodes') { + if(ref($value) eq 'HASH') { + return join(',', sort keys(%$value)); + } else { + return $value; + } + } + + return $value; +} + + +#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 if $raw eq ''; + + my $json = ''; + eval { + $json = from_json($raw); + }; + die "api response is not a json" if $@; + + return $json; +} + +1; diff --git a/src/PVE/Network/SDN/Controllers.pm b/src/PVE/Network/SDN/Controllers.pm new file mode 100644 index 0000000..a23048e --- /dev/null +++ b/src/PVE/Network/SDN/Controllers.pm @@ -0,0 +1,181 @@ +package PVE::Network::SDN::Controllers; + +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::SDN::Vnets; +use PVE::Network::SDN::Zones; + +use PVE::Network::SDN::Controllers::EvpnPlugin; +use PVE::Network::SDN::Controllers::BgpPlugin; +use PVE::Network::SDN::Controllers::FaucetPlugin; +use PVE::Network::SDN::Controllers::Plugin; +PVE::Network::SDN::Controllers::EvpnPlugin->register(); +PVE::Network::SDN::Controllers::BgpPlugin->register(); +PVE::Network::SDN::Controllers::FaucetPlugin->register(); +PVE::Network::SDN::Controllers::Plugin->init(); + + +sub sdn_controllers_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn controller 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/controllers.cfg"); + $config = cfs_read_file("sdn/controllers.cfg") if !keys %{$config->{ids}}; + return $config; +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/controllers.cfg", $cfg); +} + +sub lock_sdn_controllers_config { + my ($code, $errmsg) = @_; + + cfs_lock_file("sdn/controllers.cfg", undef, $code); + if (my $err = $@) { + $errmsg ? die "$errmsg: $err" : die $err; + } +} + +sub sdn_controllers_ids { + my ($cfg) = @_; + + return sort keys %{$cfg->{ids}}; +} + +sub complete_sdn_controller { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::running_config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_controllers_ids($cfg) ]; +} + +sub generate_controller_config { + + my $cfg = PVE::Network::SDN::running_config(); + my $vnet_cfg = $cfg->{vnets}; + my $zone_cfg = $cfg->{zones}; + my $controller_cfg = $cfg->{controllers}; + + return if !$vnet_cfg && !$zone_cfg && !$controller_cfg; + + #read main config for physical interfaces + my $current_config_file = "/etc/network/interfaces"; + my $fh = IO::File->new($current_config_file); + my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh); + $fh->close(); + + # check uplinks + my $uplinks = {}; + foreach my $id (keys %{$interfaces_config->{ifaces}}) { + my $interface = $interfaces_config->{ifaces}->{$id}; + if (my $uplink = $interface->{'uplink-id'}) { + die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink}; + $interface->{name} = $id; + $uplinks->{$interface->{'uplink-id'}} = $interface; + } + } + + # generate configuration + my $config = {}; + + foreach my $id (sort keys %{$controller_cfg->{ids}}) { + my $plugin_config = $controller_cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + $plugin->generate_controller_config($plugin_config, $controller_cfg, $id, $uplinks, $config); + } + + foreach my $id (sort keys %{$zone_cfg->{ids}}) { + my $plugin_config = $zone_cfg->{ids}->{$id}; + my $controllerid = $plugin_config->{controller}; + next if !$controllerid; + my $controller = $controller_cfg->{ids}->{$controllerid}; + if ($controller) { + my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type}); + $controller_plugin->generate_controller_zone_config($plugin_config, $controller, $controller_cfg, $id, $uplinks, $config); + } + } + + foreach my $id (sort keys %{$vnet_cfg->{ids}}) { + my $plugin_config = $vnet_cfg->{ids}->{$id}; + my $zoneid = $plugin_config->{zone}; + next if !$zoneid; + my $zone = $zone_cfg->{ids}->{$zoneid}; + next if !$zone; + my $controllerid = $zone->{controller}; + next if !$controllerid; + my $controller = $controller_cfg->{ids}->{$controllerid}; + if ($controller) { + my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type}); + $controller_plugin->generate_controller_vnet_config($plugin_config, $controller, $zone, $zoneid, $id, $config); + } + } + + return $config; +} + + +sub reload_controller { + + my $cfg = PVE::Network::SDN::running_config(); + my $controller_cfg = $cfg->{controllers}; + + return if !$controller_cfg; + + foreach my $id (keys %{$controller_cfg->{ids}}) { + my $plugin_config = $controller_cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + $plugin->reload_controller(); + } +} + +sub generate_controller_rawconfig { + my ($config) = @_; + + my $cfg = PVE::Network::SDN::running_config(); + my $controller_cfg = $cfg->{controllers}; + return if !$controller_cfg; + + my $rawconfig = ""; + foreach my $id (keys %{$controller_cfg->{ids}}) { + my $plugin_config = $controller_cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + $rawconfig .= $plugin->generate_controller_rawconfig($plugin_config, $config); + } + return $rawconfig; +} + +sub write_controller_config { + my ($config) = @_; + + my $cfg = PVE::Network::SDN::running_config(); + my $controller_cfg = $cfg->{controllers}; + return if !$controller_cfg; + + foreach my $id (keys %{$controller_cfg->{ids}}) { + my $plugin_config = $controller_cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + $plugin->write_controller_config($plugin_config, $config); + } +} + +1; + diff --git a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm new file mode 100644 index 0000000..0b8cf1a --- /dev/null +++ b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm @@ -0,0 +1,183 @@ +package PVE::Network::SDN::Controllers::BgpPlugin; + +use strict; +use warnings; + +use PVE::INotify; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Tools qw(run_command file_set_contents file_get_contents); + +use PVE::Network::SDN::Controllers::Plugin; +use PVE::Network::SDN::Zones::Plugin; +use Net::IP; + +use base('PVE::Network::SDN::Controllers::Plugin'); + +sub type { + return 'bgp'; +} + +sub properties { + return { + 'bgp-multipath-as-path-relax' => { + type => 'boolean', + optional => 1, + }, + ebgp => { + type => 'boolean', + optional => 1, + description => "Enable ebgp. (remote-as external)", + }, + 'ebgp-multihop' => { + type => 'integer', + optional => 1, + }, + loopback => { + description => "source loopback interface.", + type => 'string' + }, + node => get_standard_option('pve-node'), + }; +} + +sub options { + return { + 'node' => { optional => 0 }, + 'asn' => { optional => 0 }, + 'peers' => { optional => 0 }, + 'bgp-multipath-as-path-relax' => { optional => 1 }, + 'ebgp' => { optional => 1 }, + 'ebgp-multihop' => { optional => 1 }, + 'loopback' => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_controller_config { + my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_; + + my @peers; + @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; + + my $asn = $plugin_config->{asn}; + my $ebgp = $plugin_config->{ebgp}; + my $ebgp_multihop = $plugin_config->{'ebgp-multihop'}; + my $loopback = $plugin_config->{loopback}; + my $multipath_relax = $plugin_config->{'bgp-multipath-as-path-relax'}; + + my $local_node = PVE::INotify::nodename(); + + + return if !$asn; + return if $local_node ne $plugin_config->{node}; + + my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {}; + + my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + + my $remoteas = $ebgp ? "external" : $asn; + + #global options + my @controller_config = ( + "bgp router-id $ifaceip", + "no bgp default ipv4-unicast", + "coalesce-time 1000" + ); + + push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0; + + @controller_config = (); + if($ebgp) { + push @controller_config, "bgp disable-ebgp-connected-route-check" if $loopback; + } + + push @controller_config, "bgp bestpath as-path multipath-relax" if $multipath_relax; + + #BGP neighbors + if(@peers) { + push @controller_config, "neighbor BGP peer-group"; + push @controller_config, "neighbor BGP remote-as $remoteas"; + push @controller_config, "neighbor BGP bfd"; + push @controller_config, "neighbor BGP ebgp-multihop $ebgp_multihop" if $ebgp && $ebgp_multihop; + } + + # BGP peers + foreach my $address (@peers) { + push @controller_config, "neighbor $address peer-group BGP"; + } + push(@{$bgp->{""}}, @controller_config); + + # address-family unicast + if (@peers) { + my $ipversion = Net::IP::ip_is_ipv6($ifaceip) ? "ipv6" : "ipv4"; + my $mask = Net::IP::ip_is_ipv6($ifaceip) ? "/128" : "32"; + + push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "network $ifaceip/$mask") if $loopback; + push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP activate"); + push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP soft-reconfiguration inbound"); + } + + if ($loopback) { + $config->{frr_prefix_list}->{loopbacks_ips}->{10} = "permit 0.0.0.0/0 le 32"; + push(@{$config->{frr}->{''}}, "ip protocol bgp route-map correct_src"); + + my $routemap_config = (); + push @{$routemap_config}, "match ip address prefix-list loopbacks_ips"; + push @{$routemap_config}, "set src $ifaceip"; + my $routemap = { rule => $routemap_config, action => "permit" }; + push(@{$config->{frr_routemap}->{'correct_src'}}, $routemap); + } + + return $config; +} + +sub generate_controller_zone_config { + my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; + +} + +sub on_delete_hook { + my ($class, $controllerid, $zone_cfg) = @_; + + # verify that zone is associated to this controller + foreach my $id (keys %{$zone_cfg->{ids}}) { + my $zone = $zone_cfg->{ids}->{$id}; + die "controller $controllerid is used by $id" + if (defined($zone->{controller}) && $zone->{controller} eq $controllerid); + } +} + +sub on_update_hook { + my ($class, $controllerid, $controller_cfg) = @_; + + # we can only have 1 bgp controller by node + my $local_node = PVE::INotify::nodename(); + my $controllernb = 0; + foreach my $id (keys %{$controller_cfg->{ids}}) { + next if $id eq $controllerid; + my $controller = $controller_cfg->{ids}->{$id}; + next if $controller->{type} ne "bgp"; + next if $controller->{node} ne $local_node; + $controllernb++; + die "only 1 bgp controller can be defined" if $controllernb > 1; + } +} + +sub generate_controller_rawconfig { + my ($class, $plugin_config, $config) = @_; + return ""; +} + +sub write_controller_config { + my ($class, $plugin_config, $config) = @_; + return; +} + +sub reload_controller { + my ($class) = @_; + return; +} + +1; + + diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm new file mode 100644 index 0000000..727aeaa --- /dev/null +++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm @@ -0,0 +1,542 @@ +package PVE::Network::SDN::Controllers::EvpnPlugin; + +use strict; +use warnings; + +use PVE::INotify; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Tools qw(run_command file_set_contents file_get_contents); + +use PVE::Network::SDN::Controllers::Plugin; +use PVE::Network::SDN::Zones::Plugin; +use Net::IP; + +use base('PVE::Network::SDN::Controllers::Plugin'); + +sub type { + return 'evpn'; +} + +sub properties { + return { + asn => { + type => 'integer', + description => "autonomous system number", + minimum => 0, + maximum => 4294967296 + }, + peers => { + description => "peers address list.", + type => 'string', format => 'ip-list' + }, + }; +} + +sub options { + return { + 'asn' => { optional => 0 }, + 'peers' => { optional => 0 }, + }; +} + +# Plugin implementation +sub generate_controller_config { + my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; + + my @peers; + @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; + + my $local_node = PVE::INotify::nodename(); + + my $asn = $plugin_config->{asn}; + my $ebgp = undef; + my $loopback = undef; + my $autortas = undef; + my $bgprouter = find_bgp_controller($local_node, $controller_cfg); + if ($bgprouter) { + $ebgp = 1 if $plugin_config->{'asn'} ne $bgprouter->{asn}; + $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + $asn = $bgprouter->{asn} if $bgprouter->{asn}; + $autortas = $plugin_config->{'asn'} if $ebgp; + } + + return if !$asn; + + my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {}; + + my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + + my $remoteas = $ebgp ? "external" : $asn; + + #global options + my @controller_config = ( + "bgp router-id $ifaceip", + "no bgp default ipv4-unicast", + "coalesce-time 1000", + ); + + push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0; + + @controller_config = (); + + #VTEP neighbors + push @controller_config, "neighbor VTEP peer-group"; + push @controller_config, "neighbor VTEP remote-as $remoteas"; + push @controller_config, "neighbor VTEP bfd"; + + if($ebgp && $loopback) { + push @controller_config, "neighbor VTEP ebgp-multihop 10"; + push @controller_config, "neighbor VTEP update-source $loopback"; + } + + # VTEP peers + foreach my $address (@peers) { + next if $address eq $ifaceip; + push @controller_config, "neighbor $address peer-group VTEP"; + } + + push(@{$bgp->{""}}, @controller_config); + + # address-family l2vpn + @controller_config = (); + push @controller_config, "neighbor VTEP route-map MAP_VTEP_IN in"; + push @controller_config, "neighbor VTEP route-map MAP_VTEP_OUT out"; + push @controller_config, "neighbor VTEP activate"; + push @controller_config, "advertise-all-vni"; + push @controller_config, "autort as $autortas" if $autortas; + push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config); + + my $routemap = { rule => undef, action => "permit" }; + push(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap ); + push(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap ); + + return $config; +} + +sub generate_controller_zone_config { + my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; + + my $local_node = PVE::INotify::nodename(); + + my $vrf = "vrf_$id"; + my $vrfvxlan = $plugin_config->{'vrf-vxlan'}; + my $exitnodes = $plugin_config->{'exitnodes'}; + my $exitnodes_primary = $plugin_config->{'exitnodes-primary'}; + my $advertisesubnets = $plugin_config->{'advertise-subnets'}; + my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'}; + my $rt_import; + $rt_import = [PVE::Tools::split_list($plugin_config->{'rt-import'})] if $plugin_config->{'rt-import'}; + + my $asn = $controller->{asn}; + my @peers; + @peers = PVE::Tools::split_list($controller->{'peers'}) if $controller->{'peers'}; + my $ebgp = undef; + my $loopback = undef; + my $autortas = undef; + my $bgprouter = find_bgp_controller($local_node, $controller_cfg); + if($bgprouter) { + $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn}; + $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + $asn = $bgprouter->{asn} if $bgprouter->{asn}; + $autortas = $controller->{'asn'} if $ebgp; + } + + return if !$vrf || !$vrfvxlan || !$asn; + + my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + + # vrf + my @controller_config = (); + push @controller_config, "vni $vrfvxlan"; + push(@{$config->{frr}->{vrf}->{"$vrf"}}, @controller_config); + + #main vrf router + @controller_config = (); + push @controller_config, "bgp router-id $ifaceip"; +# push @controller_config, "!"; + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config); + + if ($autortas) { + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target import $autortas:$vrfvxlan"); + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target export $autortas:$vrfvxlan"); + } + + my $is_gateway = $exitnodes->{$local_node}; + + if ($is_gateway) { + + if (!$exitnodes_primary || $exitnodes_primary eq $local_node) { + #filter default type5 route coming from other exit nodes on primary node or both nodes if no primary is defined. + my $routemap_config = (); + push @{$routemap_config}, "match evpn route-type prefix"; + my $routemap = { rule => $routemap_config, action => "deny" }; + unshift(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap); + } elsif ($exitnodes_primary ne $local_node) { + my $routemap_config = (); + push @{$routemap_config}, "match evpn vni $vrfvxlan"; + push @{$routemap_config}, "match evpn route-type prefix"; + push @{$routemap_config}, "set metric 200"; + my $routemap = { rule => $routemap_config, action => "permit" }; + unshift(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap); + } + + + if (!$exitnodes_local_routing) { + @controller_config = (); + #import /32 routes of evpn network from vrf1 to default vrf (for packet return) + push @controller_config, "import vrf $vrf"; + push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config); + push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config); + + @controller_config = (); + #redistribute connected to be able to route to local vms on the gateway + push @controller_config, "redistribute connected"; + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config); + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config); + } + + @controller_config = (); + #add default originate to announce 0.0.0.0/0 type5 route in evpn + push @controller_config, "default-originate ipv4"; + push @controller_config, "default-originate ipv6"; + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config); + } elsif ($advertisesubnets) { + + @controller_config = (); + #redistribute connected networks + push @controller_config, "redistribute connected"; + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config); + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config); + + @controller_config = (); + #advertise connected networks type5 route in evpn + push @controller_config, "advertise ipv4 unicast"; + push @controller_config, "advertise ipv6 unicast"; + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config); + } + + if ($rt_import) { + @controller_config = (); + foreach my $rt (sort @{$rt_import}) { + push @controller_config, "route-target import $rt"; + } + push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config); + } + + return $config; +} + +sub generate_controller_vnet_config { + my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_; + + my $exitnodes = $zone->{'exitnodes'}; + my $exitnodes_local_routing = $zone->{'exitnodes-local-routing'}; + + return if !$exitnodes_local_routing; + + my $local_node = PVE::INotify::nodename(); + my $is_gateway = $exitnodes->{$local_node}; + + return if !$is_gateway; + + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); + my @controller_config = (); + foreach my $subnetid (sort keys %{$subnets}) { + my $subnet = $subnets->{$subnetid}; + my $cidr = $subnet->{cidr}; + push @controller_config, "ip route $cidr 10.255.255.2 xvrf_$zoneid"; + } + push(@{$config->{frr}->{''}}, @controller_config); +} + +sub on_delete_hook { + my ($class, $controllerid, $zone_cfg) = @_; + + # verify that zone is associated to this controller + foreach my $id (keys %{$zone_cfg->{ids}}) { + my $zone = $zone_cfg->{ids}->{$id}; + die "controller $controllerid is used by $id" + if (defined($zone->{controller}) && $zone->{controller} eq $controllerid); + } +} + +sub on_update_hook { + my ($class, $controllerid, $controller_cfg) = @_; + + # we can only have 1 evpn controller / 1 asn by server + + my $controllernb = 0; + foreach my $id (keys %{$controller_cfg->{ids}}) { + next if $id eq $controllerid; + my $controller = $controller_cfg->{ids}->{$id}; + next if $controller->{type} ne "evpn"; + $controllernb++; + die "only 1 global evpn controller can be defined" if $controllernb >= 1; + } +} + +sub find_bgp_controller { + my ($nodename, $controller_cfg) = @_; + + my $controller = undef; + foreach my $id (keys %{$controller_cfg->{ids}}) { + $controller = $controller_cfg->{ids}->{$id}; + next if $controller->{type} ne 'bgp'; + next if $controller->{node} ne $nodename; + last; + } + + return $controller; +} + + +sub sort_frr_config { + my $order = {}; + $order->{''} = 0; + $order->{'vrf'} = 1; + $order->{'ipv4 unicast'} = 1; + $order->{'ipv6 unicast'} = 2; + $order->{'l2vpn evpn'} = 3; + + my $a_val = 100; + my $b_val = 100; + + $a_val = $order->{$a} if defined($order->{$a}); + $b_val = $order->{$b} if defined($order->{$b}); + + if ($a =~ /bgp (\d+)$/) { + $a_val = 2; + } + + if ($b =~ /bgp (\d+)$/) { + $b_val = 2; + } + + return $a_val <=> $b_val; +} + +sub generate_frr_recurse{ + my ($final_config, $content, $parentkey, $level) = @_; + + my $keylist = {}; + $keylist->{vrf} = 1; + $keylist->{'address-family'} = 1; + $keylist->{router} = 1; + + my $exitkeylist = {}; + $exitkeylist->{vrf} = 1; + $exitkeylist->{'address-family'} = 1; + + my $simple_exitkeylist = {}; + $simple_exitkeylist->{router} = 1; + + # FIXME: make this generic + my $paddinglevel = undef; + if ($level == 1 || $level == 2) { + $paddinglevel = $level - 1; + } elsif ($level == 3 || $level == 4) { + $paddinglevel = $level - 2; + } + + my $padding = ""; + $padding = ' ' x ($paddinglevel) if $paddinglevel; + + if (ref $content eq 'HASH') { + foreach my $key (sort sort_frr_config keys %$content) { + if ($parentkey && defined($keylist->{$parentkey})) { + push @{$final_config}, $padding."!"; + push @{$final_config}, $padding."$parentkey $key"; + } elsif ($key ne '' && !defined($keylist->{$key})) { + push @{$final_config}, $padding."$key"; + } + + my $option = $content->{$key}; + generate_frr_recurse($final_config, $option, $key, $level+1); + + push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey}); + push @{$final_config}, $padding."exit" if $parentkey && defined($simple_exitkeylist->{$parentkey}); + } + } + + if (ref $content eq 'ARRAY') { + push @{$final_config}, map { $padding . "$_" } @$content; + } +} + +sub generate_frr_routemap { + my ($final_config, $routemaps) = @_; + + foreach my $id (sort keys %$routemaps) { + + my $routemap = $routemaps->{$id}; + my $order = 0; + foreach my $seq (@$routemap) { + $order++; + next if !defined($seq->{action}); + my @config = (); + push @config, "!"; + push @config, "route-map $id $seq->{action} $order"; + my $rule = $seq->{rule}; + push @config, map { " $_" } @$rule; + push @{$final_config}, @config; + push @{$final_config}, "exit"; + } + } +} + +sub generate_frr_list { + my ($final_config, $lists, $type) = @_; + + my $config = []; + + for my $id (sort keys %$lists) { + my $list = $lists->{$id}; + + for my $seq (sort keys %$list) { + my $rule = $list->{$seq}; + push @$config, "$type $id seq $seq $rule"; + } + } + + if (@$config > 0) { + push @{$final_config}, "!", @$config; + } +} + +sub generate_controller_rawconfig { + my ($class, $plugin_config, $config) = @_; + + my $nodename = PVE::INotify::nodename(); + + my $final_config = []; + push @{$final_config}, "frr version 8.2.2"; + push @{$final_config}, "frr defaults datacenter"; + push @{$final_config}, "hostname $nodename"; + push @{$final_config}, "log syslog informational"; + push @{$final_config}, "service integrated-vtysh-config"; + push @{$final_config}, "!"; + + if (-e "/etc/frr/frr.conf.local") { + my $local_conf = file_get_contents("/etc/frr/frr.conf.local"); + parse_merge_frr_local_config($config, $local_conf); + } + + generate_frr_recurse($final_config, $config->{frr}, undef, 0); + generate_frr_list($final_config, $config->{frr_access_list}, "access-list"); + generate_frr_list($final_config, $config->{frr_prefix_list}, "ip prefix-list"); + generate_frr_routemap($final_config, $config->{frr_routemap}); + + push @{$final_config}, "!"; + push @{$final_config}, "line vty"; + push @{$final_config}, "!"; + + my $rawconfig = join("\n", @{$final_config}); + + return if !$rawconfig; + return $rawconfig; +} + +sub parse_merge_frr_local_config { + my ($config, $local_conf) = @_; + + my $section = \$config->{""}; + my $router = undef; + my $routemap = undef; + my $routemap_config = (); + my $routemap_action = undef; + + while ($local_conf =~ /^\s*(.+?)\s*$/gm) { + my $line = $1; + $line =~ s/^\s+|\s+$//g; + + if ($line =~ m/^router (.+)$/) { + $router = $1; + $section = \$config->{'frr'}->{'router'}->{$router}->{""}; + next; + } elsif ($line =~ m/^vrf (.+)$/) { + $section = \$config->{'frr'}->{'vrf'}->{$1}; + next; + } elsif ($line =~ m/address-family (.+)$/) { + $section = \$config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1}; + next; + } elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) { + $routemap = $1; + $routemap_config = (); + $routemap_action = $2; + $section = \$config->{'frr_routemap'}->{$routemap}; + next; + } elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) { + $config->{'frr_access_list'}->{$1}->{$2} = $3; + next; + } elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) { + $config->{'frr_prefix_list'}->{$1}->{$2} = $3; + next; + } elsif($line =~ m/^exit-address-family$/) { + next; + } elsif($line =~ m/^exit$/) { + if($router) { + $section = \$config->{''}; + $router = undef; + } elsif($routemap) { + push(@{$$section}, { rule => $routemap_config, action => $routemap_action }); + $section = \$config->{''}; + $routemap = undef; + $routemap_action = undef; + $routemap_config = (); + } + next; + } elsif($line =~ m/!/) { + next; + } + + next if !$section; + if($routemap) { + push(@{$routemap_config}, $line); + } else { + push(@{$$section}, $line); + } + } +} + +sub write_controller_config { + my ($class, $plugin_config, $config) = @_; + + my $rawconfig = $class->generate_controller_rawconfig($plugin_config, $config); + return if !$rawconfig; + return if !-d "/etc/frr"; + + file_set_contents("/etc/frr/frr.conf", $rawconfig); +} + +sub reload_controller { + my ($class) = @_; + + my $conf_file = "/etc/frr/frr.conf"; + my $bin_path = "/usr/lib/frr/frr-reload.py"; + + if (!-e $bin_path) { + warn "missing $bin_path. Please install frr-pythontools package"; + return; + } + + my $err = sub { + my $line = shift; + if ($line =~ /ERROR:/) { + warn "$line \n"; + } + }; + + if (-e $conf_file && -e $bin_path) { + eval { + run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err); + }; + if ($@) { + warn "frr reload command fail. Restarting frr."; + eval { run_command(['systemctl', 'restart', 'frr']); }; + } + } +} + +1; + + diff --git a/src/PVE/Network/SDN/Controllers/FaucetPlugin.pm b/src/PVE/Network/SDN/Controllers/FaucetPlugin.pm new file mode 100644 index 0000000..4f3bb5c --- /dev/null +++ b/src/PVE/Network/SDN/Controllers/FaucetPlugin.pm @@ -0,0 +1,97 @@ +package PVE::Network::SDN::Controllers::FaucetPlugin; + +use strict; +use warnings; +use PVE::Network::SDN::Controllers::Plugin; +use PVE::Tools; +use PVE::INotify; +use PVE::JSONSchema qw(get_standard_option); +use CPAN::Meta::YAML; +use Encode; + +use base('PVE::Network::SDN::Controllers::Plugin'); + +sub type { + return 'faucet'; +} + +sub properties { + return { + }; +} + +# Plugin implementation +sub generate_controller_config { + my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; + +} + +sub generate_controller_zone_config { + my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; + + my $dpid = $plugin_config->{'dp-id'}; + my $dphex = printf("%x",$dpid); + + my $zone_config = { + dp_id => $dphex, + hardware => "Open vSwitch", + }; + + $config->{faucet}->{dps}->{$id} = $zone_config; + +} + + +sub generate_controller_vnet_config { + my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_; + + my $mac = $plugin_config->{mac}; + my $ipv4 = $plugin_config->{ipv4}; + my $ipv6 = $plugin_config->{ipv6}; + my $tag = $plugin_config->{tag}; + my $alias = $plugin_config->{alias}; + + my @ips = (); + push @ips, $ipv4 if $ipv4; + push @ips, $ipv6 if $ipv6; + + my $vlan_config = { vid => $tag }; + + $vlan_config->{description} = $alias if $alias; + $vlan_config->{faucet_mac} = $mac if $mac; + $vlan_config->{faucet_vips} = \@ips if scalar @ips > 0; + + $config->{faucet}->{vlans}->{$vnetid} = $vlan_config; + + push(@{$config->{faucet}->{routers}->{$zoneid}->{vlans}} , $vnetid); + +} + +sub write_controller_config { + my ($class, $plugin_config, $config) = @_; + + my $rawconfig = encode('UTF-8', CPAN::Meta::YAML::Dump($config->{faucet})); + + return if !$rawconfig; + return if !-d "/etc/faucet"; + + my $frr_config_file = "/etc/faucet/faucet.yaml"; + + my $writefh = IO::File->new($frr_config_file,">"); + print $writefh $rawconfig; + $writefh->close(); +} + +sub reload_controller { + my ($class) = @_; + + my $conf_file = "/etc/faucet/faucet.yaml"; + my $bin_path = "/usr/bin/faucet"; + + if (-e $conf_file && -e $bin_path) { + PVE::Tools::run_command(['systemctl', 'reload', 'faucet']); + } +} + +1; + diff --git a/src/PVE/Network/SDN/Controllers/Makefile b/src/PVE/Network/SDN/Controllers/Makefile new file mode 100644 index 0000000..11686a3 --- /dev/null +++ b/src/PVE/Network/SDN/Controllers/Makefile @@ -0,0 +1,8 @@ +SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Controllers/$$i; done diff --git a/src/PVE/Network/SDN/Controllers/Plugin.pm b/src/PVE/Network/SDN/Controllers/Plugin.pm new file mode 100644 index 0000000..c1c2cfd --- /dev/null +++ b/src/PVE/Network/SDN/Controllers/Plugin.pm @@ -0,0 +1,121 @@ +package PVE::Network::SDN::Controllers::Plugin; + +use strict; +use warnings; + +use PVE::Tools; +use PVE::JSONSchema; +use PVE::Cluster; + +use Data::Dumper; +use PVE::JSONSchema qw(get_standard_option); +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file('sdn/controllers.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); } +); + +PVE::JSONSchema::register_standard_option('pve-sdn-controller-id', { + description => "The SDN controller object identifier.", + type => 'string', format => 'pve-sdn-controller-id', +}); + +PVE::JSONSchema::register_format('pve-sdn-controller-id', \&parse_sdn_controller_id); +sub parse_sdn_controller_id { + my ($id, $noerr) = @_; + + if ($id !~ m/^[a-z][a-z0-9_-]*[a-z0-9]$/i) { + return undef if $noerr; + die "controller ID '$id' contains illegal characters\n"; + } + die "controller ID '$id' can't be more length than 64 characters\n" if length($id) > 64; + return $id; +} + +my $defaultData = { + + propertyList => { + type => { + description => "Plugin type.", + type => 'string', format => 'pve-configid', + type => 'string', + }, + controller => get_standard_option('pve-sdn-controller-id', + { completion => \&PVE::Network::SDN::complete_sdn_controller }), + }, +}; + +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 generate_sdn_config { + my ($class, $plugin_config, $node, $data, $ctime) = @_; + + die "please implement inside plugin"; +} + +sub generate_controller_config { + my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; + + die "please implement inside plugin"; +} + + +sub generate_controller_zone_config { + my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; + + die "please implement inside plugin"; +} + +sub generate_controller_vnet_config { + my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_; + +} + +sub generate_controller_rawconfig { + my ($class, $plugin_config, $config) = @_; + + die "please implement inside plugin"; +} + +sub write_controller_config { + my ($class, $plugin_config, $config) = @_; + + die "please implement inside plugin"; +} + +sub controller_reload { + my ($class) = @_; + + die "please implement inside plugin"; +} + +sub on_delete_hook { + my ($class, $controllerid, $zone_cfg) = @_; + + # do nothing by default +} + +sub on_update_hook { + my ($class, $controllerid, $controller_cfg) = @_; + + # do nothing by default +} + +1; diff --git a/src/PVE/Network/SDN/Dns.pm b/src/PVE/Network/SDN/Dns.pm new file mode 100644 index 0000000..c2e153a --- /dev/null +++ b/src/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/src/PVE/Network/SDN/Dns/Makefile b/src/PVE/Network/SDN/Dns/Makefile new file mode 100644 index 0000000..81cd2a1 --- /dev/null +++ b/src/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/src/PVE/Network/SDN/Dns/Plugin.pm b/src/PVE/Network/SDN/Dns/Plugin.pm new file mode 100644 index 0000000..07d0be1 --- /dev/null +++ b/src/PVE/Network/SDN/Dns/Plugin.pm @@ -0,0 +1,109 @@ +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 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 }, + reversev6mask => { 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, $zone, $hostname, $ip, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub add_ptr_record { + my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub del_ptr_record { + my ($class, $plugin_config, $zone, $ip, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub del_a_record { + my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub verify_zone { + my ($class, $plugin_config, $zone, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub get_reversedns_zone { + my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; + + die "please implement inside plugin"; +} + +sub on_update_hook { + my ($class, $plugin_config) = @_; +} + +1; diff --git a/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm new file mode 100644 index 0000000..096d131 --- /dev/null +++ b/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm @@ -0,0 +1,329 @@ +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 NetAddr::IP qw(:lower); +use base('PVE::Network::SDN::Dns::Plugin'); + +sub type { + return 'powerdns'; +} + +sub properties { + return { + url => { + type => 'string', + }, + key => { + type => 'string', + }, + reversemaskv6 => { + type => 'integer' + }, + }; +} + +sub options { + + return { + url => { optional => 0}, + key => { optional => 0 }, + ttl => { optional => 1 }, + reversemaskv6 => { optional => 1, description => "force a different netmask for the ipv6 reverse zone name." }, + + }; +} + +# Plugin implementation + +sub add_a_record { + my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; + + 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 $zonecontent = get_zone_content($plugin_config, $zone); + my $existing_rrset = get_zone_rrset($zonecontent, $fqdn); + + my $final_records = []; + my $foundrecord = undef; + foreach my $record (@{$existing_rrset->{records}}) { + if($record->{content} eq $ip) { + $foundrecord = 1; + next; + } + push @$final_records, $record; + } + return if $foundrecord; + + my $record = { content => $ip, + disabled => JSON::false, + name => $fqdn, + type => $type, + priority => 0 }; + + push @$final_records, $record; + + my $rrset = { name => $fqdn, + type => $type, + ttl => $ttl, + changetype => "REPLACE", + records => $final_records }; + + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error add $fqdn to zone $zone: $@" if !$noerr; + } +} + +sub add_ptr_record { + my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; + + 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]; + $hostname .= "."; + + my $reverseip = Net::IP->new($ip)->reverse_ip(); + + my $type = "PTR"; + + my $record = { content => $hostname, + 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::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error add $reverseip to zone $zone: $@" if !$noerr; + } +} + +sub del_a_record { + my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; + + 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 = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A"; + + my $zonecontent = get_zone_content($plugin_config, $zone); + my $existing_rrset = get_zone_rrset($zonecontent, $fqdn); + + my $final_records = []; + my $foundrecord = undef; + foreach my $record (@{$existing_rrset->{records}}) { + if ($record->{content} eq $ip) { + $foundrecord = 1; + next; + } + push @$final_records, $record; + } + return if !$foundrecord; + + my $rrset = {}; + + if (scalar (@{$final_records}) > 0) { + #if we still have other records, we rewrite them without removed ip + $rrset = { name => $fqdn, + type => $type, + ttl => $existing_rrset->{ttl}, + changetype => "REPLACE", + records => $final_records }; + + } else { + + $rrset = { name => $fqdn, + type => $type, + changetype => "DELETE", + records => [] }; + } + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error delete $fqdn from zone $zone: $@" if !$noerr; + } +} + +sub del_ptr_record { + my ($class, $plugin_config, $zone, $ip, $noerr) = @_; + + 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 = Net::IP->new($ip)->reverse_ip(); + + my $type = "PTR"; + + my $rrset = { name => $reverseip, + type => $type, + changetype => "DELETE", + records => [] }; + + my $params = { rrsets => [ $rrset ] }; + + eval { + PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params); + }; + + if ($@) { + die "error delete $reverseip from zone $zone: $@" if !$noerr; + } +} + +sub verify_zone { + my ($class, $plugin_config, $zone, $noerr) = @_; + + #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::api_request("GET", "$url/zones/$zone?rrsets=false", $headers); + }; + + if ($@) { + die "can't read zone $zone: $@" if !$noerr; + } +} + +sub get_reversedns_zone { + my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; + + my $cidr = $subnet->{cidr}; + my $mask = $subnet->{mask}; + + my $zone = ""; + + if (Net::IP::ip_is_ipv4($ip)) { + my ($ipblock1, $ipblock2, $ipblock3, $ipblock4) = split(/\./, $ip); + + my $ipv4 = NetAddr::IP->new($cidr); + #private addresse #powerdns built-in private zone : serve-rfc1918 + if($ipv4->is_rfc1918()) { + if ($ipblock1 == 192) { + $zone = "168.192.in-addr.arpa."; + } elsif ($ipblock1 == 172) { + $zone = "16-31.172.in-addr.arpa."; + } elsif ($ipblock1 == 10) { + $zone = "10.in-addr.arpa."; + } + + } else { + #public ipv4 : RIPE,ARIN,AFRNIC + #. Delegations can be managed in IPv4 on bit boundaries (/8, /16 or /24s), and IPv6 networks can be managed on nibble boundaries (every 4 bits of the IPv6 address) + #One or more /24 type zones need to be created if your address space has a prefix length between /17 and /24. + # If your prefix length is between /16 and /9 you will have to request one or more delegations for /16 type zones. + + if ($mask <= 24) { + $zone = "$ipblock3.$ipblock2.$ipblock1.in-addr.arpa."; + } elsif ($mask <= 16) { + $zone = "$ipblock2.$ipblock1.in-addr.arpa."; + } elsif ($mask <= 8) { + $zone = "$ipblock1.in-addr.arpa."; + } + } + } else { + $mask = $plugin_config->{reversemaskv6} if $plugin_config->{reversemaskv6}; + die "reverse dns zone mask need to be a multiple of 4" if ($mask % 4); + my $networkv6 = NetAddr::IP->new($cidr)->network(); + $zone = Net::IP->new($networkv6)->reverse_ip(); + } + + return $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::api_request("GET", "$url", $headers); + }; + + if ($@) { + die "dns api error: $@"; + } +} + + +sub get_zone_content { + my ($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]; + + my $result = undef; + eval { + $result = PVE::Network::SDN::api_request("GET", "$url/zones/$zone", $headers); + }; + + if ($@) { + die "can't read zone $zone: $@"; + } + return $result; +} + +sub get_zone_rrset { + my ($zonecontent, $name) = @_; + + my $rrsetresult = undef; + foreach my $rrset (@{$zonecontent->{rrsets}}) { + next if $rrset->{name} ne $name; + $rrsetresult = $rrset; + last; + } + return $rrsetresult; +} + +1; + + diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm new file mode 100644 index 0000000..e8a4b0b --- /dev/null +++ b/src/PVE/Network/SDN/Ipams.pm @@ -0,0 +1,69 @@ +package PVE::Network::SDN::Ipams; + +use strict; +use warnings; + +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::Ipams::PVEPlugin; +use PVE::Network::SDN::Ipams::NetboxPlugin; +use PVE::Network::SDN::Ipams::PhpIpamPlugin; +use PVE::Network::SDN::Ipams::Plugin; + +PVE::Network::SDN::Ipams::PVEPlugin->register(); +PVE::Network::SDN::Ipams::NetboxPlugin->register(); +PVE::Network::SDN::Ipams::PhpIpamPlugin->register(); +PVE::Network::SDN::Ipams::Plugin->init(); + + +sub sdn_ipams_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn ipam 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/ipams.cfg"); + #add default internal pve + $config->{ids}->{pve}->{type} = 'pve'; + return $config; +} + +sub get_plugin_config { + my ($vnet) = @_; + my $ipamid = $vnet->{ipam}; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + return $ipam_cfg->{ids}->{$ipamid}; +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/ipams.cfg", $cfg); +} + +sub sdn_ipams_ids { + my ($cfg) = @_; + + return keys %{$cfg->{ids}}; +} + +sub complete_sdn_vnet { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::Ipams::config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ]; +} + +1; + diff --git a/src/PVE/Network/SDN/Ipams/Makefile b/src/PVE/Network/SDN/Ipams/Makefile new file mode 100644 index 0000000..4e7d65f --- /dev/null +++ b/src/PVE/Network/SDN/Ipams/Makefile @@ -0,0 +1,8 @@ +SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Ipams/$$i; done diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm new file mode 100644 index 0000000..f0e7168 --- /dev/null +++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm @@ -0,0 +1,226 @@ +package PVE::Network::SDN::Ipams::NetboxPlugin; + +use strict; +use warnings; +use PVE::INotify; +use PVE::Cluster; +use PVE::Tools; + +use base('PVE::Network::SDN::Ipams::Plugin'); + +sub type { + return 'netbox'; +} + +sub properties { + return { + }; +} + +sub options { + + return { + url => { optional => 0}, + token => { optional => 0 }, + }; +} + +# Plugin implementation + +sub add_subnet { + my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $gateway = $subnet->{gateway}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + + my $internalid = get_prefix_id($url, $cidr, $headers); + + #create subnet + if (!$internalid) { + + my $params = { prefix => $cidr }; + + eval { + my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/", $headers, $params); + }; + if ($@) { + die "error add subnet to ipam: $@" if !$noerr; + } + } + +} + +sub del_subnet { + my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $gateway = $subnet->{gateway}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + + my $internalid = get_prefix_id($url, $cidr, $headers); + return if !$internalid; + + return; #fixme: check that prefix is empty exluding gateway, before delete + + eval { + PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers); + }; + if ($@) { + die "error deleting subnet from ipam: $@" if !$noerr; + } + +} + +sub add_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; + + my $mask = $subnet->{mask}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + $description .= " mac:$mac" if $mac && $description; + + my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description }; + + eval { + PVE::Network::SDN::api_request("POST", "$url/ipam/ip-addresses/", $headers, $params); + }; + + if ($@) { + if($is_gateway) { + die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr; + } else { + die "error add subnet ip to ipam: ip already exist: $@" if !$noerr; + } + } +} + +sub update_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; + + my $mask = $subnet->{mask}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + $description .= " mac:$mac" if $mac && $description; + + my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description }; + + my $ip_id = get_ip_id($url, $ip, $headers); + die "can't find ip $ip in ipam" if !$ip_id; + + eval { + PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params); + }; + if ($@) { + die "error update ip $ip : $@" if !$noerr; + } +} + +sub add_next_freeip { + my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + + my $internalid = get_prefix_id($url, $cidr, $headers); + $description .= " mac:$mac" if $mac && $description; + + my $params = { dns_name => $hostname, description => $description }; + + my $ip = undef; + eval { + my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params); + $ip = $result->{address}; + }; + + if ($@) { + die "can't find free ip in subnet $cidr: $@" if !$noerr; + } + + return $ip; +} + +sub del_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_; + + return if !$ip; + + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + + my $ip_id = get_ip_id($url, $ip, $headers); + die "can't find ip $ip in ipam" if !$ip_id; + + eval { + PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers); + }; + if ($@) { + die "error delete ip $ip : $@" if !$noerr; + } +} + +sub verify_api { + my ($class, $plugin_config) = @_; + + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"]; + + + eval { + PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers); + }; + if ($@) { + die "Can't connect to netbox api: $@"; + } +} + +sub on_update_hook { + my ($class, $plugin_config) = @_; + + PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config); +} + +#helpers + +sub get_prefix_id { + my ($url, $cidr, $headers) = @_; + + my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers); + my $data = @{$result->{results}}[0]; + my $internalid = $data->{id}; + return $internalid; +} + +sub get_ip_id { + my ($url, $ip, $headers) = @_; + my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers); + my $data = @{$result->{results}}[0]; + my $ip_id = $data->{id}; + return $ip_id; +} + +sub is_ip_gateway { + my ($url, $ip, $headers) = @_; + my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers); + my $data = @{$result->{data}}[0]; + my $description = $data->{description}; + my $is_gateway = 1 if $description eq 'gateway'; + return $is_gateway; +} + +1; + + diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm new file mode 100644 index 0000000..3e8ffc5 --- /dev/null +++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm @@ -0,0 +1,210 @@ +package PVE::Network::SDN::Ipams::PVEPlugin; + +use strict; +use warnings; +use PVE::INotify; +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file cfs_lock_file); +use PVE::Tools; +use JSON; +use NetAddr::IP qw(:lower); + +use Net::IP; +use Digest::SHA; + +use base('PVE::Network::SDN::Ipams::Plugin'); + + +my $ipamdb_file = "priv/ipam.db"; + +PVE::Cluster::cfs_register_file($ipamdb_file, + sub { PVE::Network::SDN::Ipams::PVEPlugin->parse_config(@_); }, + sub { PVE::Network::SDN::Ipams::PVEPlugin->write_config(@_); }); + +sub type { + return 'pve'; +} + +sub properties { +} + +sub options { +} + +# Plugin implementation + +sub add_subnet { + my ($class, $plugin_config, $subnetid, $subnet) = @_; + + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + my $gateway = $subnet->{gateway}; + + + cfs_lock_file($ipamdb_file, undef, sub { + my $db = {}; + $db = read_db(); + + $db->{zones}->{$zone} = {} if !$db->{zones}->{$zone}; + my $zonedb = $db->{zones}->{$zone}; + + if(!$zonedb->{subnets}->{$cidr}) { + #create subnet + $zonedb->{subnets}->{$cidr}->{ips} = {}; + write_db($db); + } + }); + die "$@" if $@; +} + +sub del_subnet { + my ($class, $plugin_config, $subnetid, $subnet) = @_; + + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + + cfs_lock_file($ipamdb_file, undef, sub { + + my $db = read_db(); + + my $dbzone = $db->{zones}->{$zone}; + die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; + + die "cannot delete subnet '$cidr', not empty\n" if keys %{$dbsubnet->{ips}} > 0; + + delete $dbzone->{subnets}->{$cidr}; + + write_db($db); + }); + die "$@" if $@; + +} + +sub add_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_; + + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + + cfs_lock_file($ipamdb_file, undef, sub { + + my $db = read_db(); + my $dbzone = $db->{zones}->{$zone}; + die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; + + die "IP '$ip' already exist\n" if (!$is_gateway && defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && defined($dbsubnet->{ips}->{$ip}) && !defined($dbsubnet->{ips}->{$ip}->{gateway})); + $dbsubnet->{ips}->{$ip} = {}; + $dbsubnet->{ips}->{$ip} = {gateway => 1} if $is_gateway; + + write_db($db); + }); + die "$@" if $@; +} + +sub update_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_; + return; +} + +sub add_next_freeip { + my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description) = @_; + + my $cidr = $subnet->{cidr}; + my $network = $subnet->{network}; + my $zone = $subnet->{zone}; + my $mask = $subnet->{mask}; + my $freeip = undef; + + cfs_lock_file($ipamdb_file, undef, sub { + + my $db = read_db(); + my $dbzone = $db->{zones}->{$zone}; + die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + die "subnet '$cidr' doesn't exist in IPAM DB" if !$dbsubnet; + + if (Net::IP::ip_is_ipv4($network) && $mask == 32) { + die "cannot find free IP in subnet '$cidr'\n" if defined($dbsubnet->{ips}->{$network}); + $freeip = $network; + } else { + my $iplist = NetAddr::IP->new($cidr); + my $lastip = $iplist->last()->canon(); + $iplist++ if Net::IP::ip_is_ipv4($network); #skip network address for ipv4 + while(1) { + my $ip = $iplist->canon(); + if (defined($dbsubnet->{ips}->{$ip})) { + last if $ip eq $lastip; + $iplist++; + next; + } + $freeip = $ip; + last; + } + } + + die "can't find free ip in subnet '$cidr'\n" if !$freeip; + + $dbsubnet->{ips}->{$freeip} = {}; + + write_db($db); + }); + die "$@" if $@; + + return "$freeip/$mask"; +} + +sub del_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; + + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + + cfs_lock_file($ipamdb_file, undef, sub { + + my $db = read_db(); + die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone}; + my $dbzone = $db->{zones}->{$zone}; + die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr}; + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + + die "IP '$ip' does not exist in IPAM DB\n" if !defined($dbsubnet->{ips}->{$ip}); + delete $dbsubnet->{ips}->{$ip}; + + write_db($db); + }); + die "$@" if $@; +} + +#helpers + +sub read_db { + my $db = cfs_read_file($ipamdb_file); + return $db; +} + +sub write_db { + my ($cfg) = @_; + + my $json = to_json($cfg); + cfs_write_file($ipamdb_file, $json); +} + +sub write_config { + my ($class, $filename, $cfg) = @_; + + return $cfg; +} + +sub parse_config { + my ($class, $filename, $raw) = @_; + + $raw = '{}' if !defined($raw) ||$raw eq ''; + my $cfg = from_json($raw); + + return $cfg; +} + +1; diff --git a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm new file mode 100644 index 0000000..ad5286b --- /dev/null +++ b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm @@ -0,0 +1,259 @@ +package PVE::Network::SDN::Ipams::PhpIpamPlugin; + +use strict; +use warnings; +use PVE::INotify; +use PVE::Cluster; +use PVE::Tools; + +use base('PVE::Network::SDN::Ipams::Plugin'); + +sub type { + return 'phpipam'; +} + +sub properties { + return { + url => { + type => 'string', + }, + token => { + type => 'string', + }, + section => { + type => 'integer', + }, + }; +} + +sub options { + + return { + url => { optional => 0}, + token => { optional => 0 }, + section => { optional => 0 }, + }; +} + +# Plugin implementation + +sub add_subnet { + my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $network = $subnet->{network}; + my $mask = $subnet->{mask}; + + my $gateway = $subnet->{gateway}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + #search subnet + my $internalid = get_prefix_id($url, $cidr, $headers); + + #create subnet + if (!$internalid) { + my $params = { subnet => $network, + mask => $mask, + sectionId => $section, + }; + + eval { + PVE::Network::SDN::api_request("POST", "$url/subnets/", $headers, $params); + }; + if ($@) { + die "error add subnet to ipam: $@" if !$noerr; + } + } + +} + +sub del_subnet { + my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + my $internalid = get_prefix_id($url, $cidr, $headers); + return if !$internalid; + + return; #fixme: check that prefix is empty exluding gateway, before delete + + eval { + PVE::Network::SDN::api_request("DELETE", "$url/subnets/$internalid", $headers); + }; + if ($@) { + die "error deleting subnet from ipam: $@" if !$noerr; + } + +} + +sub add_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + my $internalid = get_prefix_id($url, $cidr, $headers); + + my $params = { ip => $ip, + subnetId => $internalid, + hostname => $hostname, + description => $description, + }; + $params->{is_gateway} = 1 if $is_gateway; + $params->{mac} = $mac if $mac; + + eval { + PVE::Network::SDN::api_request("POST", "$url/addresses/", $headers, $params); + }; + + if ($@) { + if($is_gateway) { + die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr; + } else { + die "error add subnet ip to ipam: ip $ip already exist: $@" if !$noerr; + } + } +} + +sub update_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + my $ip_id = get_ip_id($url, $ip, $headers); + die "can't find ip addresse in ipam" if !$ip_id; + + my $params = { + hostname => $hostname, + description => $description, + }; + $params->{is_gateway} = 1 if $is_gateway; + $params->{mac} = $mac if $mac; + + eval { + PVE::Network::SDN::api_request("PATCH", "$url/addresses/$ip_id", $headers, $params); + }; + + if ($@) { + die "ipam: error update subnet ip $ip: $@" if !$noerr; + } +} + +sub add_next_freeip { + my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_; + + my $cidr = $subnet->{cidr}; + my $mask = $subnet->{mask}; + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $section = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + my $internalid = get_prefix_id($url, $cidr, $headers); + + my $params = { hostname => $hostname, + description => $description, + }; + + $params->{mac} = $mac if $mac; + + my $ip = undef; + eval { + my $result = PVE::Network::SDN::api_request("POST", "$url/addresses/first_free/$internalid/", $headers, $params); + $ip = $result->{data}; + }; + + if ($@) { + die "can't find free ip in subnet $cidr: $@" if !$noerr; + } + + return "$ip/$mask" if $ip && $mask; +} + +sub del_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_; + + return if !$ip; + + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + my $ip_id = get_ip_id($url, $ip, $headers); + return if !$ip_id; + + eval { + PVE::Network::SDN::api_request("DELETE", "$url/addresses/$ip_id", $headers); + }; + if ($@) { + die "error delete ip $ip: $@" if !$noerr; + } +} + +sub verify_api { + my ($class, $plugin_config) = @_; + + my $url = $plugin_config->{url}; + my $token = $plugin_config->{token}; + my $sectionid = $plugin_config->{section}; + my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; + + eval { + PVE::Network::SDN::api_request("GET", "$url/sections/$sectionid", $headers); + }; + if ($@) { + die "Can't connect to phpipam api: $@"; + } +} + +sub on_update_hook { + my ($class, $plugin_config) = @_; + + PVE::Network::SDN::Ipams::PhpIpamPlugin::verify_api($class, $plugin_config); +} + + +#helpers + +sub get_prefix_id { + my ($url, $cidr, $headers) = @_; + + my $result = PVE::Network::SDN::api_request("GET", "$url/subnets/cidr/$cidr", $headers); + my $data = @{$result->{data}}[0]; + my $internalid = $data->{id}; + return $internalid; +} + +sub get_ip_id { + my ($url, $ip, $headers) = @_; + my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers); + my $data = @{$result->{data}}[0]; + my $ip_id = $data->{id}; + return $ip_id; +} + +sub is_ip_gateway { + my ($url, $ip, $headers) = @_; + my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers); + my $data = @{$result->{data}}[0]; + my $is_gateway = $data->{is_gateway}; + return $is_gateway; +} + +1; + + diff --git a/src/PVE/Network/SDN/Ipams/Plugin.pm b/src/PVE/Network/SDN/Ipams/Plugin.pm new file mode 100644 index 0000000..c96eeda --- /dev/null +++ b/src/PVE/Network/SDN/Ipams/Plugin.pm @@ -0,0 +1,111 @@ +package PVE::Network::SDN::Ipams::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/ipams.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }); + +PVE::JSONSchema::register_standard_option('pve-sdn-ipam-id', { + description => "The SDN ipam object identifier.", + type => 'string', format => 'pve-sdn-ipam-id', +}); + +PVE::JSONSchema::register_format('pve-sdn-ipam-id', \&parse_sdn_ipam_id); +sub parse_sdn_ipam_id { + my ($id, $noerr) = @_; + + if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { + return undef if $noerr; + die "ipam ID '$id' contains illegal characters\n"; + } + return $id; +} + +my $defaultData = { + + propertyList => { + type => { + description => "Plugin type.", + type => 'string', format => 'pve-configid', + type => 'string', + }, + ipam => get_standard_option('pve-sdn-ipam-id', + { completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipam }), + }, +}; + +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_subnet { + my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub del_subnet { + my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub add_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub update_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_; + # only update ip attributes (mac,hostname,..). Don't change the ip addresses itself, as some ipam + # don't allow ip address change without del/add + + die "please implement inside plugin"; +} + +sub add_next_freeip { + my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub del_ip { + my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_; + + die "please implement inside plugin"; +} + +sub on_update_hook { + my ($class, $plugin_config) = @_; +} + +1; diff --git a/src/PVE/Network/SDN/Makefile b/src/PVE/Network/SDN/Makefile new file mode 100644 index 0000000..92cfcd0 --- /dev/null +++ b/src/PVE/Network/SDN/Makefile @@ -0,0 +1,13 @@ +SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/$$i; done + make -C Controllers install + make -C Zones install + make -C Ipams install + make -C Dns install + diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm new file mode 100644 index 0000000..15b370f --- /dev/null +++ b/src/PVE/Network/SDN/SubnetPlugin.pm @@ -0,0 +1,166 @@ +package PVE::Network::SDN::SubnetPlugin; + +use strict; +use warnings; + +use Net::IP; +use Net::Subnet qw(subnet_matcher); + +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Exception qw(raise raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::Network::SDN::Ipams; +use PVE::Network::SDN::Vnets; + +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file('sdn/subnets.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }); + +PVE::JSONSchema::register_standard_option('pve-sdn-subnet-id', { + description => "The SDN subnet object identifier.", + type => 'string', format => 'pve-sdn-subnet-id', + type => 'string' +}); + +PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id); +sub parse_sdn_subnet_id { + my ($id, $noerr) = @_; + + my $cidr = ""; + if($id =~ /\//) { + $cidr = $id; + } else { + my ($zone, $ip, $mask) = split(/-/, $id); + $cidr = "$ip/$mask"; + } + + if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) || + PVE::JSONSchema::pve_verify_cidrv6($cidr, 1))) + { + return undef if $noerr; + die "value does not look like a valid CIDR network\n"; + } + return $id; +} + +my $defaultData = { + + propertyList => { + subnet => get_standard_option('pve-sdn-subnet-id', + { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }), + }, +}; + +sub type { + return 'subnet'; +} + +sub private { + return $defaultData; +} + +sub properties { + return { + vnet => { + type => 'string', + description => "associated vnet", + }, + gateway => { + type => 'string', format => 'ip', + description => "Subnet Gateway: Will be assign on vnet for layer3 zones", + }, + snat => { + type => 'boolean', + description => "enable masquerade for this subnet if pve-firewall", + }, +# #cloudinit, dhcp options +# routes => { +# type => 'string', +# description => "static routes [network=:gateway=,network=:gateway=,... ]", +# }, + dnszoneprefix => { + type => 'string', format => 'dns-name', + description => "dns domain zone prefix ex: 'adm' -> .adm.mydomain.com", + }, + }; +} + +sub options { + return { + vnet => { optional => 0 }, + gateway => { optional => 1 }, +# routes => { optional => 1 }, + snat => { optional => 1 }, + dnszoneprefix => { optional => 1 }, + }; +} + +sub on_update_hook { + my ($class, $zone, $subnetid, $subnet, $old_subnet) = @_; + + my $cidr = $subnet->{cidr}; + my $mask = $subnet->{mask}; + + my $subnet_matcher = subnet_matcher($cidr); + + my $vnetid = $subnet->{vnet}; + my $gateway = $subnet->{gateway}; + my $ipam = $zone->{ipam}; + my $dns = $zone->{dns}; + my $dnszone = $zone->{dnszone}; + my $reversedns = $zone->{reversedns}; + + my $old_gateway = $old_subnet->{gateway} if $old_subnet; + my $mac = undef; + + if($vnetid) { + my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid); + raise_param_exc({ vnet => "$vnetid don't exist"}) if !$vnet; + raise_param_exc({ vnet => "you can't add a subnet on a vlanaware vnet"}) if $vnet->{vlanaware}; + $mac = $vnet->{mac}; + } + + my $pointopoint = 1 if Net::IP::ip_is_ipv4($gateway) && $mask == 32; + + #for /32 pointopoint, we allow gateway outside the subnet + raise_param_exc({ gateway => "$gateway is not in subnet $cidr"}) if $gateway && !$subnet_matcher->($gateway) && !$pointopoint; + + + if ($ipam) { + PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet); + + #don't register gateway for pointopoint + return if $pointopoint; + + #delete gateway on removal + if (!defined($gateway) && $old_gateway) { + eval { + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway); + }; + warn if $@; + } + if(!$old_gateway || $gateway && $gateway ne $old_gateway) { + my $hostname = "$vnetid-gw"; + my $description = "gateway"; + PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, $description, 1); + } + + #delete old gateway after update + if($gateway && $old_gateway && $gateway ne $old_gateway) { + eval { + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway); + }; + warn if $@; + } + } +} + +sub on_delete_hook { + my ($class, $subnetid, $subnet_cfg, $vnet_cfg) = @_; + + return; +} + +1; diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm new file mode 100644 index 0000000..6bb42e5 --- /dev/null +++ b/src/PVE/Network/SDN/Subnets.pm @@ -0,0 +1,375 @@ +package PVE::Network::SDN::Subnets; + +use strict; +use warnings; + +use Net::Subnet qw(subnet_matcher); +use Net::IP; +use NetAddr::IP qw(:lower); + +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Network::SDN::Dns; +use PVE::Network::SDN::Ipams; + +use PVE::Network::SDN::SubnetPlugin; +PVE::Network::SDN::SubnetPlugin->register(); +PVE::Network::SDN::SubnetPlugin->init(); + +sub sdn_subnets_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn subnet ID specified\n" if !$id; + + my $scfg = $cfg->{ids}->{$id}; + die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg); + + if($scfg) { + my ($zone, $network, $mask) = split(/-/, $id); + $scfg->{cidr} = "$network/$mask"; + $scfg->{zone} = $zone; + $scfg->{network} = $network; + $scfg->{mask} = $mask; + } + + return $scfg; +} + +sub config { + my $config = cfs_read_file("sdn/subnets.cfg"); +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/subnets.cfg", $cfg); +} + +sub sdn_subnets_ids { + my ($cfg) = @_; + + return sort keys %{$cfg->{ids}}; +} + +sub complete_sdn_subnet { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::Subnets::config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ]; +} + +sub get_subnet { + my ($subnetid, $running) = @_; + + my $cfg = {}; + if($running) { + my $cfg = PVE::Network::SDN::running_config(); + $cfg = $cfg->{subnets}; + } else { + $cfg = PVE::Network::SDN::Subnets::config(); + } + + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1); + return $subnet; +} + +sub find_ip_subnet { + my ($ip, $mask, $subnets) = @_; + + my $subnet = undef; + my $subnetid = undef; + + foreach my $id (sort keys %{$subnets}) { + + next if $mask ne $subnets->{$id}->{mask}; + my $cidr = $subnets->{$id}->{cidr}; + my $subnet_matcher = subnet_matcher($cidr); + next if !$subnet_matcher->($ip); + $subnet = $subnets->{$id}; + $subnetid = $id; + last; + } + die "can't find any subnet for ip $ip" if !$subnet; + + return ($subnetid, $subnet); +} + +sub verify_dns_zone { + 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); +} + +sub get_reversedns_zone { + my ($subnetid, $subnet, $dns, $ip) = @_; + + return if !$subnetid || !$dns || !$ip; + + 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->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip); +} + +sub add_dns_record { + my ($zone, $dns, $hostname, $ip) = @_; + return if !$zone || !$dns || !$hostname || !$ip; + + 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->add_a_record($plugin_config, $zone, $hostname, $ip); + +} + +sub add_dns_ptr_record { + my ($reversezone, $zone, $dns, $hostname, $ip) = @_; + + return if !$zone || !$reversezone || !$dns || !$hostname || !$ip; + + $hostname .= ".$zone"; + 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->add_ptr_record($plugin_config, $reversezone, $hostname, $ip); +} + +sub del_dns_record { + my ($zone, $dns, $hostname, $ip) = @_; + + return if !$zone || !$dns || !$hostname || !$ip; + + 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->del_a_record($plugin_config, $zone, $hostname, $ip); +} + +sub del_dns_ptr_record { + my ($reversezone, $dns, $ip) = @_; + + return if !$reversezone || !$dns || !$ip; + + 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->del_ptr_record($plugin_config, $reversezone, $ip); +} + +sub add_subnet { + my ($zone, $subnetid, $subnet) = @_; + + my $ipam = $zone->{ipam}; + return if !$ipam; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $plugin_config = $ipam_cfg->{ids}->{$ipam}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $plugin->add_subnet($plugin_config, $subnetid, $subnet); +} + +sub del_subnet { + my ($zone, $subnetid, $subnet) = @_; + + my $ipam = $zone->{ipam}; + return if !$ipam; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $plugin_config = $ipam_cfg->{ids}->{$ipam}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $plugin->del_subnet($plugin_config, $subnetid, $subnet); +} + +sub next_free_ip { + my ($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns) = @_; + + my $cidr = undef; + my $ip = undef; + $description = '' if !$description; + + my $ipamid = $zone->{ipam}; + my $dns = $zone->{dns}; + my $dnszone = $zone->{dnszone}; + my $reversedns = $zone->{reversedns}; + my $dnszoneprefix = $subnet->{dnszoneprefix}; + + $hostname .= ".$dnszoneprefix" if $dnszoneprefix; + + #verify dns zones before ipam + verify_dns_zone($dnszone, $dns) if !$skipdns; + + 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}); + eval { + $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description); + ($ip, undef) = split(/\//, $cidr); + }; + die $@ if $@; + } + + eval { + my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); + + if(!$skipdns) { + #add dns + add_dns_record($dnszone, $dns, $hostname, $ip); + #add reverse dns + add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip); + } + }; + if ($@) { + #rollback + my $err = $@; + eval { + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname) + }; + die $err; + } + return $cidr; +} + +sub add_ip { + my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $skipdns) = @_; + + return if !$subnet || !$ip; + + my $ipaddr = NetAddr::IP->new($ip); + $ip = $ipaddr->canon(); + + my $ipamid = $zone->{ipam}; + my $dns = $zone->{dns}; + my $dnszone = $zone->{dnszone}; + my $reversedns = $zone->{reversedns}; + my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); + my $dnszoneprefix = $subnet->{dnszoneprefix}; + + $hostname .= ".$dnszoneprefix" if $dnszoneprefix; + + #verify dns zones before ipam + if(!$skipdns) { + 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}); + + eval { + $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway); + }; + die $@ if $@; + } + + eval { + if(!$skipdns) { + #add dns + add_dns_record($dnszone, $dns, $hostname, $ip); + #add reverse dns + add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip); + } + }; + if ($@) { + #rollback + my $err = $@; + eval { + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname) + }; + die $err; + } +} + +sub update_ip { + my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns) = @_; + + return if !$subnet || !$ip; + + my $ipaddr = NetAddr::IP->new($ip); + $ip = $ipaddr->canon(); + + my $ipamid = $zone->{ipam}; + my $dns = $zone->{dns}; + my $dnszone = $zone->{dnszone}; + my $reversedns = $zone->{reversedns}; + my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); + my $dnszoneprefix = $subnet->{dnszoneprefix}; + + $hostname .= ".$dnszoneprefix" if $dnszoneprefix; + + #verify dns zones before ipam + if(!$skipdns) { + 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}); + eval { + $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description); + }; + die $@ if $@; + } + + return if $hostname eq $oldhostname; + + eval { + if(!$skipdns) { + #add dns + del_dns_record($dnszone, $dns, $oldhostname, $ip); + add_dns_record($dnszone, $dns, $hostname, $ip); + #add reverse dns + del_dns_ptr_record($reversednszone, $reversedns, $ip); + add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip); + } + }; +} + +sub del_ip { + my ($zone, $subnetid, $subnet, $ip, $hostname, $skipdns) = @_; + + return if !$subnet || !$ip; + + my $ipaddr = NetAddr::IP->new($ip); + $ip = $ipaddr->canon(); + + my $ipamid = $zone->{ipam}; + my $dns = $zone->{dns}; + my $dnszone = $zone->{dnszone}; + my $reversedns = $zone->{reversedns}; + my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); + my $dnszoneprefix = $subnet->{dnszoneprefix}; + $hostname .= ".$dnszoneprefix" if $dnszoneprefix; + + if(!$skipdns) { + 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, $subnet, $ip); + } + + eval { + if(!$skipdns) { + del_dns_record($dnszone, $dns, $hostname, $ip); + del_dns_ptr_record($reversednszone, $reversedns, $ip); + } + }; + if ($@) { + warn $@; + } +} + +1; diff --git a/src/PVE/Network/SDN/VnetPlugin.pm b/src/PVE/Network/SDN/VnetPlugin.pm new file mode 100644 index 0000000..062904c --- /dev/null +++ b/src/PVE/Network/SDN/VnetPlugin.pm @@ -0,0 +1,109 @@ +package PVE::Network::SDN::VnetPlugin; + +use strict; +use warnings; + +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Exception qw(raise raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); + +use PVE::SectionConfig; +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file('sdn/vnets.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }); + +PVE::JSONSchema::register_standard_option('pve-sdn-vnet-id', { + description => "The SDN vnet object identifier.", + type => 'string', format => 'pve-sdn-vnet-id', +}); + +PVE::JSONSchema::register_format('pve-sdn-vnet-id', \&parse_sdn_vnet_id); +sub parse_sdn_vnet_id { + my ($id, $noerr) = @_; + + if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { + return undef if $noerr; + die "vnet ID '$id' contains illegal characters\n"; + } + die "vnet ID '$id' can't be more length than 8 characters\n" if length($id) > 8; + return $id; +} + +my $defaultData = { + + propertyList => { + vnet => get_standard_option('pve-sdn-vnet-id', + { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }), + }, +}; + +sub type { + return 'vnet'; +} + +sub private { + return $defaultData; +} + +sub properties { + return { + zone => { + type => 'string', + description => "zone id", + }, + type => { + description => "Type", + optional => 1, + }, + tag => { + type => 'integer', + description => "vlan or vxlan id", + }, + vlanaware => { + type => 'boolean', + description => 'Allow vm VLANs to pass through this vnet.', + }, + alias => { + type => 'string', + description => "alias name of the vnet", + pattern => qr/[\(\)-_.\w\d\s]{0,256}/i, + maxLength => 256, + optional => 1, + }, + }; +} + +sub options { + return { + zone => { optional => 0}, + tag => { optional => 1}, + alias => { optional => 1 }, + vlanaware => { optional => 1 }, + }; +} + +sub on_delete_hook { + my ($class, $vnetid, $vnet_cfg) = @_; + + #verify if subnets are associated + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid); + raise_param_exc({ vnet => "Can't delete vnet if subnets exists"}) if $subnets; +} + +sub on_update_hook { + my ($class, $vnetid, $vnet_cfg) = @_; + + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + my $tag = $vnet->{tag}; + my $vlanaware = $vnet->{vlanaware}; + + #don't allow vlanaware change if subnets are defined + if($vnet->{vlanaware}) { + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid); + raise_param_exc({ vlanaware => "vlanaware vnet is not compatible with subnets"}) if $subnets; + } +} + +1; diff --git a/src/PVE/Network/SDN/Vnets.pm b/src/PVE/Network/SDN/Vnets.pm new file mode 100644 index 0000000..0b32c58 --- /dev/null +++ b/src/PVE/Network/SDN/Vnets.pm @@ -0,0 +1,163 @@ +package PVE::Network::SDN::Vnets; + +use strict; +use warnings; + +use Net::IP; + +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Network::SDN; +use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::Zones; + +use PVE::Network::SDN::VnetPlugin; +PVE::Network::SDN::VnetPlugin->register(); +PVE::Network::SDN::VnetPlugin->init(); + +sub sdn_vnets_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn vnet ID specified\n" if !$id; + + my $scfg = $cfg->{ids}->{$id}; + die "sdn vnet '$id' does not exist\n" if (!$noerr && !$scfg); + + return $scfg; +} + +sub config { + return cfs_read_file("sdn/vnets.cfg"); +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/vnets.cfg", $cfg); +} + +sub sdn_vnets_ids { + my ($cfg) = @_; + + return sort keys %{$cfg->{ids}}; +} + +sub complete_sdn_vnet { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::Vnets::config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg) ]; +} + +sub get_vnet { + my ($vnetid, $running) = @_; + + return if !$vnetid; + + my $scfg = {}; + if($running) { + my $cfg = PVE::Network::SDN::running_config(); + $scfg = $cfg->{vnets}; + } else { + $scfg = PVE::Network::SDN::Vnets::config(); + } + + my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($scfg, $vnetid, 1); + + return $vnet; +} + +sub get_subnets { + my ($vnetid) = @_; + + return if !$vnetid; + + my $subnets = undef; + my $subnets_cfg = PVE::Network::SDN::Subnets::config(); + foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) { + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); + next if !$subnet->{vnet} || $subnet->{vnet} ne $vnetid; + $subnets->{$subnetid} = $subnet; + } + return $subnets; + +} + +sub get_subnet_from_vnet_cidr { + my ($vnetid, $cidr) = @_; + + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); + my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid); + my $zoneid = $vnet->{zone}; + my $zone = PVE::Network::SDN::Zones::get_zone($zoneid); + + my ($ip, $mask) = split(/\//, $cidr); + die "ip address is not in cidr format" if !$mask; + + my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $mask, $subnets); + + return ($zone, $subnetid, $subnet, $ip); +} + +sub get_next_free_cidr { + my ($vnetid, $hostname, $mac, $description, $ipversion, $skipdns) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid); + my $zoneid = $vnet->{zone}; + my $zone = PVE::Network::SDN::Zones::get_zone($zoneid); + + return if !$zone->{ipam}; + + $ipversion = 4 if !$ipversion; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); + my $ip = undef; + my $subnetcount = 0; + + foreach my $subnetid (sort keys %{$subnets}) { + my $subnet = $subnets->{$subnetid}; + my $network = $subnet->{network}; + + next if $ipversion != Net::IP::ip_get_version($network); + $subnetcount++; + + eval { + $ip = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns); + }; + warn $@ if $@; + last if $ip; + } + die "can't find any free ip" if !$ip && $subnetcount > 0; + + return $ip; +} + +sub add_cidr { + my ($vnetid, $cidr, $hostname, $mac, $description, $skipdns) = @_; + + return if !$vnetid; + + my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr); + PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, undef, $skipdns); +} + +sub update_cidr { + my ($vnetid, $cidr, $hostname, $oldhostname, $mac, $description, $skipdns) = @_; + + return if !$vnetid; + + my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr); + PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns); +} + +sub del_cidr { + my ($vnetid, $cidr, $hostname, $skipdns) = @_; + + return if !$vnetid; + + my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr); + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $skipdns); +} + + + +1; diff --git a/src/PVE/Network/SDN/Zones.pm b/src/PVE/Network/SDN/Zones.pm new file mode 100644 index 0000000..f8e40b1 --- /dev/null +++ b/src/PVE/Network/SDN/Zones.pm @@ -0,0 +1,357 @@ +package PVE::Network::SDN::Zones; + +use strict; +use warnings; + +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::Vnets; +use PVE::Network::SDN::Zones::VlanPlugin; +use PVE::Network::SDN::Zones::QinQPlugin; +use PVE::Network::SDN::Zones::VxlanPlugin; +use PVE::Network::SDN::Zones::EvpnPlugin; +use PVE::Network::SDN::Zones::FaucetPlugin; +use PVE::Network::SDN::Zones::SimplePlugin; +use PVE::Network::SDN::Zones::Plugin; + +PVE::Network::SDN::Zones::VlanPlugin->register(); +PVE::Network::SDN::Zones::QinQPlugin->register(); +PVE::Network::SDN::Zones::VxlanPlugin->register(); +PVE::Network::SDN::Zones::EvpnPlugin->register(); +PVE::Network::SDN::Zones::FaucetPlugin->register(); +PVE::Network::SDN::Zones::SimplePlugin->register(); +PVE::Network::SDN::Zones::Plugin->init(); + +my $local_network_sdn_file = "/etc/network/interfaces.d/sdn"; + +sub sdn_zones_config { + my ($cfg, $id, $noerr) = @_; + + die "no sdn zone 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/zones.cfg"); + return $config; +} + +sub get_plugin_config { + my ($vnet) = @_; + my $zoneid = $vnet->{zone}; + my $zone_cfg = PVE::Network::SDN::Zones::config(); + return $zone_cfg->{ids}->{$zoneid}; +} + +sub write_config { + my ($cfg) = @_; + + cfs_write_file("sdn/zones.cfg", $cfg); +} + +sub sdn_zones_ids { + my ($cfg) = @_; + + return sort keys %{$cfg->{ids}}; +} + +sub complete_sdn_zone { + my ($cmdname, $pname, $cvalue) = @_; + + my $cfg = PVE::Network::SDN::running_config(); + + return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ]; +} + +sub get_zone { + my ($zoneid, $running) = @_; + + my $cfg = {}; + if($running) { + my $cfg = PVE::Network::SDN::running_config(); + $cfg = $cfg->{vnets}; + } else { + $cfg = PVE::Network::SDN::Zones::config(); + } + + my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1); + + return $zone; +} + + +sub generate_etc_network_config { + + my $cfg = PVE::Network::SDN::running_config(); + + my $version = $cfg->{version}; + my $vnet_cfg = $cfg->{vnets}; + my $zone_cfg = $cfg->{zones}; + my $subnet_cfg = $cfg->{subnets}; + my $controller_cfg = $cfg->{controllers}; + return if !$vnet_cfg && !$zone_cfg; + + my $interfaces_config = PVE::INotify::read_file('interfaces'); + + #generate configuration + my $config = {}; + my $nodename = PVE::INotify::nodename(); + + for my $id (sort keys %{$vnet_cfg->{ids}}) { + my $vnet = $vnet_cfg->{ids}->{$id}; + my $zone = $vnet->{zone}; + + if (!$zone) { + warn "can't generate vnet '$id': no zone assigned!\n"; + next; + } + + my $plugin_config = $zone_cfg->{ids}->{$zone}; + + if (!defined($plugin_config)) { + warn "can't generate vnet '$id': zone $zone don't exist\n"; + next; + } + + next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename}; + + my $controller; + if (my $controllerid = $plugin_config->{controller}) { + $controller = $controller_cfg->{ids}->{$controllerid}; + } + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + eval { + $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config); + }; + if (my $err = $@) { + warn "zone $zone : vnet $id : $err\n"; + next; + } + } + + my $raw_network_config = "\#version:$version\n"; + foreach my $iface (sort keys %$config) { + $raw_network_config .= "\n"; + $raw_network_config .= "auto $iface\n"; + $raw_network_config .= "iface $iface\n"; + foreach my $option (@{$config->{$iface}}) { + $raw_network_config .= "\t$option\n"; + } + } + + return $raw_network_config; +} + +sub write_etc_network_config { + my ($rawconfig) = @_; + + return if !$rawconfig; + + my $writefh = IO::File->new($local_network_sdn_file,">"); + print $writefh $rawconfig; + $writefh->close(); +} + +sub read_etc_network_config_version { + my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file); + + return if !defined($versionstr); + + if ($versionstr =~ m/^\#version:(\d+)$/) { + return $1; + } +} + +sub ifquery_check { + + my $cmd = ['ifquery', '-a', '-c', '-o','json']; + + my $result = ''; + my $reader = sub { $result .= shift }; + + eval { + run_command($cmd, outfunc => $reader); + }; + + my $resultjson = decode_json($result); + my $interfaces = {}; + + foreach my $interface (@$resultjson) { + my $name = $interface->{name}; + $interfaces->{$name} = { + status => $interface->{status}, + config => $interface->{config}, + config_status => $interface->{config_status}, + }; + } + + return $interfaces; +} + +my $warned_about_reload; + +sub status { + + my $err_config = undef; + + my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version(); + my $cfg = PVE::Network::SDN::running_config(); + my $sdn_version = $cfg->{version}; + + return if !$sdn_version; + + if (!$local_version) { + $err_config = "local sdn network configuration is not yet generated, please reload"; + if (!$warned_about_reload) { + $warned_about_reload = 1; + warn "$err_config\n"; + } + } elsif ($local_version < $sdn_version) { + $err_config = "local sdn network configuration is too old, please reload"; + if (!$warned_about_reload) { + $warned_about_reload = 1; + warn "$err_config\n"; + } + } else { + $warned_about_reload = 0; + } + + my $status = ifquery_check(); + + my $vnet_cfg = $cfg->{vnets}; + my $zone_cfg = $cfg->{zones}; + my $nodename = PVE::INotify::nodename(); + + my $vnet_status = {}; + my $zone_status = {}; + + for my $id (sort keys %{$zone_cfg->{ids}}) { + next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename}; + $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available'; + } + + foreach my $id (sort keys %{$vnet_cfg->{ids}}) { + my $vnet = $vnet_cfg->{ids}->{$id}; + my $zone = $vnet->{zone}; + next if !defined($zone); + + my $plugin_config = $zone_cfg->{ids}->{$zone}; + + if (!defined($plugin_config)) { + $vnet_status->{$id}->{status} = 'error'; + $vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured"; + next; + } + + next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename}; + + $vnet_status->{$id}->{zone} = $zone; + $vnet_status->{$id}->{status} = 'available'; + + if ($err_config) { + $vnet_status->{$id}->{status} = 'pending'; + $vnet_status->{$id}->{statusmsg} = $err_config; + next; + } + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status); + if (@{$err_msg} > 0) { + $vnet_status->{$id}->{status} = 'error'; + $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg}); + $zone_status->{$id}->{status} = 'error'; + } + } + + return ($zone_status, $vnet_status); +} + +sub tap_create { + my ($iface, $bridge) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); + if (!$vnet) { # fallback for classic bridge + PVE::Network::tap_create($iface, $bridge); + return; + } + + my $plugin_config = get_plugin_config($vnet); + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->tap_create($plugin_config, $vnet, $iface, $bridge); +} + +sub veth_create { + my ($veth, $vethpeer, $bridge, $hwaddr) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); + if (!$vnet) { # fallback for classic bridge + PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr); + return; + } + + my $plugin_config = get_plugin_config($vnet); + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr); +} + +sub tap_plug { + my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); + if (!$vnet) { # fallback for classic bridge + my $interfaces_config = PVE::INotify::read_file('interfaces'); + my $opts = {}; + $opts->{learning} = 0 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'}; + PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts); + return; + } + + my $plugin_config = get_plugin_config($vnet); + my $nodename = PVE::INotify::nodename(); + + die "vnet $bridge is not allowed on this node\n" + if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename}); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate); +} + +sub add_bridge_fdb { + my ($iface, $macaddr, $bridge, $firewall) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); + if (!$vnet) { # fallback for classic bridge + PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall); + return; + } + + my $plugin_config = get_plugin_config($vnet); + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'}; +} + +sub del_bridge_fdb { + my ($iface, $macaddr, $bridge, $firewall) = @_; + + my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); + if (!$vnet) { # fallback for classic bridge + PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall); + return; + } + + my $plugin_config = get_plugin_config($vnet); + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'}; +} + +1; + diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm new file mode 100644 index 0000000..a5a7539 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm @@ -0,0 +1,315 @@ +package PVE::Network::SDN::Zones::EvpnPlugin; + +use strict; +use warnings; +use PVE::Network::SDN::Zones::VxlanPlugin; +use PVE::Exception qw(raise raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::Tools qw($IPV4RE); +use PVE::INotify; +use PVE::Cluster; +use PVE::Tools; +use Net::IP; + +use PVE::Network::SDN::Controllers::EvpnPlugin; + +use base('PVE::Network::SDN::Zones::VxlanPlugin'); + +sub type { + return 'evpn'; +} + +PVE::JSONSchema::register_format('pve-sdn-bgp-rt', \&pve_verify_sdn_bgp_rt); +sub pve_verify_sdn_bgp_rt { + my ($rt) = @_; + + if ($rt =~ m/^(\d+):(\d+)$/) { + my $asn = $1; + my $id = $2; + + if ($asn < 0 || $asn > 4294967295) { + die "value does not look like a valid bgp route-target\n"; + } + if ($id < 0 || $id > 4294967295) { + die "value does not look like a valid bgp route-target\n"; + } + } else { + die "value does not look like a valid bgp route-target\n"; + } + return $rt; +} + +sub properties { + return { + 'vrf-vxlan' => { + type => 'integer', + description => "l3vni.", + }, + 'controller' => { + type => 'string', + description => "Frr router name", + }, + 'mac' => { + type => 'string', + description => "Anycast logical router mac address", + optional => 1, format => 'mac-addr' + }, + 'exitnodes' => get_standard_option('pve-node-list'), + 'exitnodes-local-routing' => { + type => 'boolean', + description => "Allow exitnodes to connect to evpn guests", + optional => 1 + }, + 'exitnodes-primary' => get_standard_option('pve-node', { + description => "Force traffic to this exitnode first."}), + 'advertise-subnets' => { + type => 'boolean', + description => "Advertise evpn subnets if you have silent hosts", + optional => 1 + }, + 'disable-arp-nd-suppression' => { + type => 'boolean', + description => "Disable ipv4 arp && ipv6 neighbour discovery suppression", + optional => 1 + }, + 'rt-import' => { + type => 'string', + description => "Route-Target import", + optional => 1, format => 'pve-sdn-bgp-rt-list' + } + }; +} + +sub options { + return { + nodes => { optional => 1}, + 'vrf-vxlan' => { optional => 0 }, + controller => { optional => 0 }, + exitnodes => { optional => 1 }, + 'exitnodes-local-routing' => { optional => 1 }, + 'exitnodes-primary' => { optional => 1 }, + 'advertise-subnets' => { optional => 1 }, + 'disable-arp-nd-suppression' => { optional => 1 }, + 'rt-import' => { optional => 1 }, + mtu => { optional => 1 }, + mac => { optional => 1 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + my $tag = $vnet->{tag}; + my $alias = $vnet->{alias}; + my $mac = $plugin_config->{'mac'}; + + my $vrf_iface = "vrf_$zoneid"; + my $vrfvxlan = $plugin_config->{'vrf-vxlan'}; + my $local_node = PVE::INotify::nodename(); + + die "missing vxlan tag" if !$tag; + die "missing controller" if !$controller; + warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware}; + + my @peers = PVE::Tools::split_list($controller->{'peers'}); + my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg); + my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + my $is_evpn_gateway = $plugin_config->{'exitnodes'}->{$local_node}; + my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'}; + + + my $mtu = 1450; + $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu}; + $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; + + #vxlan interface + my $vxlan_iface = "vxlan_$vnetid"; + my @iface_config = (); + push @iface_config, "vxlan-id $tag"; + push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip; + push @iface_config, "bridge-learning off"; + push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'}; + + push @iface_config, "mtu $mtu" if $mtu; + push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface}; + + #vnet bridge + @iface_config = (); + + my $address = {}; + my $ipv4 = undef; + my $ipv6 = undef; + my $enable_forward_v4 = undef; + my $enable_forward_v6 = undef; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); + foreach my $subnetid (sort keys %{$subnets}) { + my $subnet = $subnets->{$subnetid}; + my $cidr = $subnet->{cidr}; + my $mask = $subnet->{mask}; + + my $gateway = $subnet->{gateway}; + if ($gateway) { + push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway}); + $address->{$gateway} = 1; + } + + my $iptables = undef; + my $checkrouteip = undef; + my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4; + + if ($ipversion == 6) { + $ipv6 = 1; + $iptables = "ip6tables"; + $checkrouteip = '2001:4860:4860::8888'; + $enable_forward_v6 = 1 if $gateway; + } else { + $ipv4 = 1; + $iptables = "iptables"; + $checkrouteip = '8.8.8.8'; + $enable_forward_v4 = 1 if $gateway; + } + + if ($subnet->{snat}) { + + #find outgoing interface + my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip); + if ($outip && $outiface && $is_evpn_gateway) { + #use snat, faster than masquerade + push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; + push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; + #add conntrack zone once on outgoing interface + push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1"; + push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1"; + } + } + } + + push @iface_config, "hwaddress $mac" if $mac; + push @iface_config, "bridge_ports $vxlan_iface"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "alias $alias" if $alias; + push @iface_config, "ip-forward on" if $enable_forward_v4; + push @iface_config, "ip6-forward on" if $enable_forward_v6; + push @iface_config, "arp-accept on" if $ipv4||$ipv6; + push @iface_config, "vrf $vrf_iface" if $vrf_iface; + push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; + + if ($vrf_iface) { + #vrf interface + @iface_config = (); + push @iface_config, "vrf-table auto"; + if(!$is_evpn_gateway) { + push @iface_config, "post-up ip route add vrf $vrf_iface unreachable default metric 4278198272"; + } else { + push @iface_config, "post-up ip route del vrf $vrf_iface unreachable default metric 4278198272"; + } + + push(@{$config->{$vrf_iface}}, @iface_config) if !$config->{$vrf_iface}; + + if ($vrfvxlan) { + #l3vni vxlan interface + my $iface_vrf_vxlan = "vrfvx_$zoneid"; + @iface_config = (); + push @iface_config, "vxlan-id $vrfvxlan"; + push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip; + push @iface_config, "bridge-learning off"; + push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'}; + push @iface_config, "mtu $mtu" if $mtu; + push(@{$config->{$iface_vrf_vxlan}}, @iface_config) if !$config->{$iface_vrf_vxlan}; + + #l3vni bridge + my $brvrf = "vrfbr_$zoneid"; + @iface_config = (); + push @iface_config, "bridge-ports $iface_vrf_vxlan"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "vrf $vrf_iface"; + push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf}; + } + + if ( $is_evpn_gateway && $exitnodes_local_routing ) { + #add a veth pair for local cross-vrf routing + my $iface_xvrf = "xvrf_$zoneid"; + my $iface_xvrfp = "xvrfp_$zoneid"; + + @iface_config = (); + push @iface_config, "link-type veth"; + push @iface_config, "address 10.255.255.1/30"; + push @iface_config, "veth-peer-name $iface_xvrfp"; + push @iface_config, "mtu ".($mtu+50) if $mtu; + push(@{$config->{$iface_xvrf}}, @iface_config) if !$config->{$iface_xvrf}; + + @iface_config = (); + push @iface_config, "link-type veth"; + push @iface_config, "address 10.255.255.2/30"; + push @iface_config, "veth-peer-name $iface_xvrf"; + push @iface_config, "vrf $vrf_iface"; + push @iface_config, "mtu ".($mtu+50) if $mtu; + push(@{$config->{$iface_xvrfp}}, @iface_config) if !$config->{$iface_xvrfp}; + } + } + return $config; +} + +sub on_update_hook { + my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_; + + # verify that controller exist + my $controller = $zone_cfg->{ids}->{$zoneid}->{controller}; + if (!defined($controller_cfg->{ids}->{$controller})) { + die "controller $controller don't exist"; + } else { + die "$controller is not a evpn controller type" if $controller_cfg->{ids}->{$controller}->{type} ne 'evpn'; + } + + #vrf-vxlan need to be defined + + my $vrfvxlan = $zone_cfg->{ids}->{$zoneid}->{'vrf-vxlan'}; + # verify that vrf-vxlan is not already declared in another zone + foreach my $id (keys %{$zone_cfg->{ids}}) { + next if $id eq $zoneid; + die "vrf-vxlan $vrfvxlan is already declared in $id" + if (defined($zone_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $zone_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan); + } + + if (!defined($zone_cfg->{ids}->{$zoneid}->{'mac'})) { + my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); + $zone_cfg->{ids}->{$zoneid}->{'mac'} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); + } +} + + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + my $tag = $vnet->{tag}; + + raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag); + raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216; + + # verify that tag is not already defined globally (vxlan-id are unique) + foreach my $id (keys %{$vnet_cfg->{ids}}) { + next if $id eq $vnetid; + my $othervnet = $vnet_cfg->{ids}->{$id}; + my $other_tag = $othervnet->{tag}; + my $other_zoneid = $othervnet->{zone}; + my $other_zone = $zone_cfg->{ids}->{$other_zoneid}; + next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn'; + raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag; + } +} + + +1; + + diff --git a/src/PVE/Network/SDN/Zones/FaucetPlugin.pm b/src/PVE/Network/SDN/Zones/FaucetPlugin.pm new file mode 100644 index 0000000..a237d17 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/FaucetPlugin.pm @@ -0,0 +1,74 @@ +package PVE::Network::SDN::Zones::FaucetPlugin; + +use strict; +use warnings; +use PVE::Network::SDN::Zones::VlanPlugin; + +use base('PVE::Network::SDN::Zones::VlanPlugin'); + +sub type { + return 'faucet'; +} + +sub properties { + return { + 'dp-id' => { + type => 'integer', + description => 'Faucet dataplane id', + }, + }; +} + +sub options { + + return { + nodes => { optional => 1}, + 'dp-id' => { optional => 0 }, +# 'uplink-id' => { optional => 0 }, + 'controller' => { optional => 0 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $controller, $config) = @_; + + my $mtu = $vnet->{mtu}; + my $uplink = $plugin_config->{'uplink-id'}; + my $dpid = $plugin_config->{'dp-id'}; + my $dphex = printf("%x",$dpid); #fixme :should be 16characters hex + + my $iface = $uplinks->{$uplink}->{name}; + $iface = "uplink${uplink}" if !$iface; + + #tagged interface + my @iface_config = (); + push @iface_config, "ovs_type OVSPort"; + push @iface_config, "ovs_bridge $zoneid"; + push @iface_config, "ovs_mtu $mtu" if $mtu; + push(@{$config->{$iface}}, @iface_config) if !$config->{$iface}; + + #vnet bridge + @iface_config = (); + push @iface_config, "ovs_port $iface"; + push @iface_config, "ovs_type OVSBridge"; + push @iface_config, "ovs_mtu $mtu" if $mtu; + + push @iface_config, "ovs_extra set bridge $zoneid other-config:datapath-id=$dphex"; + push @iface_config, "ovs_extra set bridge $zoneid other-config:disable-in-band=true"; + push @iface_config, "ovs_extra set bridge $zoneid fail_mode=secure"; + push @iface_config, "ovs_extra set-controller $vnetid tcp:127.0.0.1:6653"; + + push(@{$config->{$zoneid}}, @iface_config) if !$config->{$zoneid}; + + return $config; +} + + +1; + + diff --git a/src/PVE/Network/SDN/Zones/Makefile b/src/PVE/Network/SDN/Zones/Makefile new file mode 100644 index 0000000..8454388 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/Makefile @@ -0,0 +1,8 @@ +SOURCES=Plugin.pm VlanPlugin.pm VxlanPlugin.pm FaucetPlugin.pm EvpnPlugin.pm QinQPlugin.pm SimplePlugin.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Zones/$$i; done diff --git a/src/PVE/Network/SDN/Zones/Plugin.pm b/src/PVE/Network/SDN/Zones/Plugin.pm new file mode 100644 index 0000000..2c707b3 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/Plugin.pm @@ -0,0 +1,340 @@ +package PVE::Network::SDN::Zones::Plugin; + +use strict; +use warnings; + +use PVE::Tools qw(run_command); +use PVE::JSONSchema; +use PVE::Cluster; +use PVE::Network; + +use PVE::JSONSchema qw(get_standard_option); +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file( + 'sdn/zones.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }, +); + +PVE::JSONSchema::register_standard_option('pve-sdn-zone-id', { + description => "The SDN zone object identifier.", + type => 'string', format => 'pve-sdn-zone-id', +}); + +PVE::JSONSchema::register_format('pve-sdn-zone-id', \&parse_sdn_zone_id); +sub parse_sdn_zone_id { + my ($id, $noerr) = @_; + + if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) { + return undef if $noerr; + die "zone ID '$id' contains illegal characters\n"; + } + die "zone ID '$id' can't be more length than 8 characters\n" if length($id) > 8; + return $id; +} + +my $defaultData = { + + propertyList => { + type => { + description => "Plugin type.", + type => 'string', format => 'pve-configid', + type => 'string', + }, + nodes => get_standard_option('pve-node-list', { optional => 1 }), + zone => get_standard_option('pve-sdn-zone-id', { + completion => \&PVE::Network::SDN::Zones::complete_sdn_zone, + }), + ipam => { + type => 'string', + description => "use a specific ipam", + optional => 1, + }, + }, +}; + +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 decode_value { + my ($class, $type, $key, $value) = @_; + + if ($key eq 'nodes' || $key eq 'exitnodes') { + my $res = {}; + + foreach my $node (PVE::Tools::split_list($value)) { + if (PVE::JSONSchema::pve_verify_node_name($node)) { + $res->{$node} = 1; + } + } + + return $res; + } + + return $value; +} + +sub encode_value { + my ($class, $type, $key, $value) = @_; + + if ($key eq 'nodes' || $key eq 'exitnodes') { + return join(',', keys(%$value)); + } + + return $value; +} + +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + die "please implement inside plugin"; +} + +sub generate_controller_config { + my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_; + + die "please implement inside plugin"; +} + +sub generate_controller_vnet_config { + my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_; + +} + +sub write_controller_config { + my ($class, $plugin_config, $config) = @_; + + die "please implement inside plugin"; +} + +sub controller_reload { + my ($class) = @_; + + die "please implement inside plugin"; +} + +sub on_delete_hook { + my ($class, $zoneid, $vnet_cfg) = @_; + + # verify that no vnet are associated to this zone + foreach my $id (keys %{$vnet_cfg->{ids}}) { + my $vnet = $vnet_cfg->{ids}->{$id}; + die "zone $zoneid is used by vnet $id" + if ($vnet->{type} eq 'vnet' && defined($vnet->{zone}) && $vnet->{zone} eq $zoneid); + } +} + +sub on_update_hook { + my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_; + + # do nothing by default +} + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + # do nothing by default +} + +#helpers +sub parse_tag_number_or_range { + my ($str, $max, $tag) = @_; + + my @elements = split(/,/, $str); + my $count = 0; + my $allowed = undef; + + die "extraneous commas in list\n" if $str ne join(',', @elements); + foreach my $item (@elements) { + if ($item =~ m/^([0-9]+)-([0-9]+)$/) { + $count += 2; + my ($port1, $port2) = ($1, $2); + die "invalid port '$port1'\n" if $port1 > $max; + die "invalid port '$port2'\n" if $port2 > $max; + die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2; + + if ($tag && $tag >= $port1 && $tag <= $port2){ + $allowed = 1; + last; + } + + } elsif ($item =~ m/^([0-9]+)$/) { + $count += 1; + my $port = $1; + die "invalid port '$port'\n" if $port > $max; + + if ($tag && $tag == $port){ + $allowed = 1; + last; + } + } + } + die "tag $tag is not allowed" if $tag && !$allowed; + + return (scalar(@elements) > 1); +} + +sub status { + my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; + + my $err_msg = []; + + # ifaces to check + my $ifaces = [ $vnetid ]; + + foreach my $iface (@{$ifaces}) { + if (!$status->{$iface}->{status}) { + push @$err_msg, "missing $iface"; + } elsif ($status->{$iface}->{status} ne 'pass') { + push @$err_msg, "error $iface"; + } + } + return $err_msg; +} + + +sub tap_create { + my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_; + + PVE::Network::tap_create($iface, $vnetid); +} + +sub veth_create { + my ($class, $plugin_config, $vnet, $veth, $vethpeer, $vnetid, $hwaddr) = @_; + + PVE::Network::veth_create($veth, $vethpeer, $vnetid, $hwaddr); +} + +sub tap_plug { + my ($class, $plugin_config, $vnet, $tag, $iface, $vnetid, $firewall, $trunks, $rate) = @_; + + my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$vnetid/bridge/vlan_filtering"); + die "vm vlans are not allowed on vnet $vnetid" if !$vlan_aware && ($tag || $trunks); + + my $opts = {}; + $opts->{learning} = 0 if $plugin_config->{'bridge-disable-mac-learning'}; + PVE::Network::tap_plug($iface, $vnetid, $tag, $firewall, $trunks, $rate, $opts); +} + +#helper + +sub get_uplink_iface { + my ($interfaces_config, $uplink) = @_; + + my $iface = undef; + foreach my $id (keys %{$interfaces_config->{ifaces}}) { + my $interface = $interfaces_config->{ifaces}->{$id}; + if (my $iface_uplink = $interface->{'uplink-id'}) { + next if $iface_uplink ne $uplink; + if($interface->{type} ne 'eth' && $interface->{type} ne 'bond') { + warn "uplink $uplink is not a physical or bond interface"; + next; + } + $iface = $id; + } + } + + #create a dummy uplink interface if no uplink found + if(!$iface) { + warn "can't find uplink $uplink in physical interface"; + $iface = "uplink${uplink}"; + } + + return $iface; +} + +sub get_local_route_ip { + my ($targetip) = @_; + + my $ip = undef; + my $interface = undef; + + run_command(['/sbin/ip', 'route', 'get', $targetip], outfunc => sub { + if ($_[0] =~ m/src ($PVE::Tools::IPRE)/) { + $ip = $1; + } + if ($_[0] =~ m/dev (\S+)/) { + $interface = $1; + } + + }); + return ($ip, $interface); +} + + +sub find_local_ip_interface_peers { + my ($peers, $iface) = @_; + + my $network_config = PVE::INotify::read_file('interfaces'); + my $ifaces = $network_config->{ifaces}; + + #if iface is defined, return ip if exist (if not,try to find it on other ifaces) + if ($iface) { + my $ip = $ifaces->{$iface}->{address}; + return ($ip,$iface) if $ip; + } + + #is a local ip member of peers list ? + foreach my $address (@{$peers}) { + while (my $interface = each %$ifaces) { + my $ip = $ifaces->{$interface}->{address}; + if ($ip && $ip eq $address) { + return ($ip, $interface); + } + } + } + + #if peer is remote, find source with ip route + foreach my $address (@{$peers}) { + my ($ip, $interface) = get_local_route_ip($address); + return ($ip, $interface); + } +} + +sub find_bridge { + my ($bridge) = @_; + + die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge"; +} + +sub is_vlanaware { + my ($bridge) = @_; + + return PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering"); +} + +sub is_ovs { + my ($bridge) = @_; + + my $is_ovs = !-d "/sys/class/net/$bridge/brif"; + return $is_ovs; +} + +sub get_bridge_ifaces { + my ($bridge) = @_; + + my @bridge_ifaces = (); + my $dir = "/sys/class/net/$bridge/brif"; + PVE::Tools::dir_glob_foreach($dir, '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', sub { + push @bridge_ifaces, $_[0]; + }); + + return @bridge_ifaces; +} +1; diff --git a/src/PVE/Network/SDN/Zones/QinQPlugin.pm b/src/PVE/Network/SDN/Zones/QinQPlugin.pm new file mode 100644 index 0000000..f4d12bc --- /dev/null +++ b/src/PVE/Network/SDN/Zones/QinQPlugin.pm @@ -0,0 +1,233 @@ +package PVE::Network::SDN::Zones::QinQPlugin; + +use strict; +use warnings; + +use PVE::Exception qw(raise raise_param_exc); + +use PVE::Network::SDN::Zones::Plugin; + +use base('PVE::Network::SDN::Zones::Plugin'); + +sub type { + return 'qinq'; +} + +sub properties { + return { + tag => { + type => 'integer', + minimum => 0, + description => "Service-VLAN Tag", + }, + mtu => { + type => 'integer', + description => "MTU", + optional => 1, + }, + 'vlan-protocol' => { + type => 'string', + enum => ['802.1q', '802.1ad'], + default => '802.1q', + optional => 1, + } + }; +} + +sub options { + return { + nodes => { optional => 1}, + 'tag' => { optional => 0 }, + 'bridge' => { optional => 0 }, + 'bridge-disable-mac-learning' => { optional => 1 }, + 'mtu' => { optional => 1 }, + 'vlan-protocol' => { optional => 1 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + my ($bridge, $mtu, $stag) = $plugin_config->@{'bridge', 'mtu', 'tag'}; + my $vlanprotocol = $plugin_config->{'vlan-protocol'}; + + PVE::Network::SDN::Zones::Plugin::find_bridge($bridge); + + my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); + my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); + + my @iface_config = (); + my $zone_notag_uplink = "ln_${zoneid}"; + my $zone_notag_uplinkpeer = "pr_${zoneid}"; + my $zone = "z_${zoneid}"; + + my $vnet_bridge_ports = ""; + if (my $ctag = $vnet->{tag}) { + $vnet_bridge_ports = "$zone.$ctag"; + } else { + $vnet_bridge_ports = $zone_notag_uplinkpeer; + } + + my $zone_bridge_ports = ""; + if ($is_ovs) { + # ovs--->ovsintport(dot1q-tunnel tag)------->vlanawarebrige-----(tag)--->vnet + + $vlanprotocol = "802.1q" if !$vlanprotocol; + my $svlan_iface = "sv_".$zoneid; + + # ovs dot1q-tunnel port + @iface_config = (); + push @iface_config, "ovs_type OVSIntPort"; + push @iface_config, "ovs_bridge $bridge"; + push @iface_config, "ovs_mtu $mtu" if $mtu; + push @iface_config, "ovs_options vlan_mode=dot1q-tunnel tag=$stag other_config:qinq-ethtype=$vlanprotocol"; + push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; + + # redefine main ovs bridge, ifupdown2 will merge ovs_ports + @{$config->{$bridge}}[0] = "ovs_ports" if !@{$config->{$bridge}}[0]; + my @ovs_ports = split / / , @{$config->{$bridge}}[0]; + @{$config->{$bridge}}[0] .= " $svlan_iface" if !grep( $_ eq $svlan_iface, @ovs_ports ); + + $zone_bridge_ports = $svlan_iface; + + } elsif ($vlan_aware) { + # VLAN_aware_brige-(tag)----->vlanwarebridge-(tag)----->vnet + + if ($vlanprotocol) { + @iface_config = (); + push @iface_config, "bridge-vlan-protocol $vlanprotocol"; + push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge}; + } + + $zone_bridge_ports = "$bridge.$stag"; + + } else { + # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet + + my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge); + + for my $bridge_iface (@bridge_ifaces) { + # use named vlan interface to avoid too long names + my $svlan_iface = "sv_$zoneid"; + + # svlan + @iface_config = (); + push @iface_config, "vlan-raw-device $bridge_iface"; + push @iface_config, "vlan-id $stag"; + push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol; + push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface}; + + $zone_bridge_ports = $svlan_iface; + last; + } + } + + # veth peer for notag vnet + @iface_config = (); + push @iface_config, "link-type veth"; + push @iface_config, "veth-peer-name $zone_notag_uplinkpeer"; + push(@{$config->{$zone_notag_uplink}}, @iface_config) if !$config->{$zone_notag_uplink}; + + @iface_config = (); + push @iface_config, "link-type veth"; + push @iface_config, "veth-peer-name $zone_notag_uplink"; + push(@{$config->{$zone_notag_uplinkpeer}}, @iface_config) if !$config->{$zone_notag_uplinkpeer}; + + # zone vlan aware bridge + @iface_config = (); + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "bridge-stp off"; + push @iface_config, "bridge-ports $zone_bridge_ports $zone_notag_uplink"; + push @iface_config, "bridge-fd 0"; + push @iface_config, "bridge-vlan-aware yes"; + push @iface_config, "bridge-vids 2-4094"; + push(@{$config->{$zone}}, @iface_config) if !$config->{$zone}; + + # vnet bridge + @iface_config = (); + push @iface_config, "bridge_ports $vnet_bridge_ports"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + if($vnet->{vlanaware}) { + push @iface_config, "bridge-vlan-aware yes"; + push @iface_config, "bridge-vids 2-4094"; + } + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "alias $vnet->{alias}" if $vnet->{alias}; + push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; +} + +sub status { + my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; + + my $bridge = $plugin_config->{bridge}; + my $err_msg = []; + + if (!-d "/sys/class/net/$bridge") { + push @$err_msg, "missing $bridge"; + return $err_msg; + } + + my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); + + my $tag = $vnet->{tag}; + my $vnet_uplink = "ln_".$vnetid; + my $vnet_uplinkpeer = "pr_".$vnetid; + my $zone_notag_uplink = "ln_".$zone; + my $zone_notag_uplinkpeer = "pr_".$zone; + my $zonebridge = "z_$zone"; + + # ifaces to check + my $ifaces = [ $vnetid, $bridge ]; + + push @$ifaces, $zonebridge; + push @$ifaces, $zone_notag_uplink; + push @$ifaces, $zone_notag_uplinkpeer; + + if (!$vlan_aware) { + my $svlan_iface = "sv_$zone"; + push @$ifaces, $svlan_iface; + } + + foreach my $iface (@{$ifaces}) { + if (!$status->{$iface}->{status}) { + push @$err_msg, "missing $iface"; + } elsif ($status->{$iface}->{status} ne 'pass') { + push @$err_msg, "error $iface"; + } + } + return $err_msg; +} + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + + my $tag = $vnet->{tag}; + raise_param_exc({ tag => "VLAN tag maximal value is 4096" }) if $tag && $tag > 4096; + + # verify that tag is not already defined in another vnet on same zone + for my $id (sort keys %{$vnet_cfg->{ids}}) { + next if $id eq $vnetid; + my $other_vnet = $vnet_cfg->{ids}->{$id}; + next if $vnet->{zone} ne $other_vnet->{zone}; + my $other_tag = $other_vnet->{tag}; + if ($tag) { + raise_param_exc({ tag => "tag $tag already exist in zone $vnet->{zone} vnet $id"}) + if $other_tag && $tag eq $other_tag; + } else { + raise_param_exc({ tag => "tag-less vnet already exists in zone $vnet->{zone} vnet $id"}) + if !$other_tag; + } + } +} + +1; + + diff --git a/src/PVE/Network/SDN/Zones/SimplePlugin.pm b/src/PVE/Network/SDN/Zones/SimplePlugin.pm new file mode 100644 index 0000000..7757747 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/SimplePlugin.pm @@ -0,0 +1,159 @@ +package PVE::Network::SDN::Zones::SimplePlugin; + +use strict; +use warnings; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Exception qw(raise raise_param_exc); +use PVE::Cluster; +use PVE::Tools; + +use base('PVE::Network::SDN::Zones::Plugin'); + +sub type { + return 'simple'; +} + +sub properties { + return { + dns => { + type => 'string', + description => "dns api server", + }, + reversedns => { + type => 'string', + description => "reverse dns api server", + }, + dnszone => { + type => 'string', format => 'dns-name', + description => "dns domain zone ex: mydomain.com", + } + }; +} + +sub options { + return { + nodes => { optional => 1}, + mtu => { optional => 1 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + return $config if$config->{$vnetid}; # nothing to do + + my $mac = $vnet->{mac}; + my $alias = $vnet->{alias}; + my $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; + + # vnet bridge + my @iface_config = (); + + my $address = {}; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1); + + my $ipv4 = undef; + my $ipv6 = undef; + my $enable_forward_v4 = undef; + my $enable_forward_v6 = undef; + + foreach my $subnetid (sort keys %{$subnets}) { + my $subnet = $subnets->{$subnetid}; + my $cidr = $subnet->{cidr}; + my $mask = $subnet->{mask}; + + my $gateway = $subnet->{gateway}; + if ($gateway) { + push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway}); + $address->{$gateway} = 1; + } + + my $iptables = undef; + my $checkrouteip = undef; + my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4; + + if ( $ipversion == 6) { + $ipv6 = 1; + $iptables = "ip6tables"; + $checkrouteip = '2001:4860:4860::8888'; + $enable_forward_v6 = 1 if $gateway; + } else { + $ipv4 = 1; + $iptables = "iptables"; + $checkrouteip = '8.8.8.8'; + $enable_forward_v4 = 1 if $gateway; + } + + #add route for /32 pointtopoint + push @iface_config, "up ip route add $cidr dev $vnetid" if $mask == 32 && $ipversion == 4; + if ($subnet->{snat}) { + #find outgoing interface + my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip); + if ($outip && $outiface) { + #use snat, faster than masquerade + push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; + push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip"; + #add conntrack zone once on outgoing interface + push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1"; + push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1"; + } + } + } + + push @iface_config, "hwaddress $mac" if $mac; + push @iface_config, "bridge_ports none"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + if ($vnet->{vlanaware}) { + push @iface_config, "bridge-vlan-aware yes"; + push @iface_config, "bridge-vids 2-4094"; + } + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "alias $alias" if $alias; + push @iface_config, "ip-forward on" if $enable_forward_v4; + push @iface_config, "ip6-forward on" if $enable_forward_v6; + + push @{$config->{$vnetid}}, @iface_config; + + return $config; +} + +sub status { + my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; + + # ifaces to check + my $ifaces = [ $vnetid ]; + my $err_msg = []; + foreach my $iface (@{$ifaces}) { + if (!$status->{$iface}->{status}) { + push @$err_msg, "missing $iface"; + } elsif ($status->{$iface}->{status} ne 'pass') { + push @$err_msg, "error iface $iface"; + } + } + return $err_msg; +} + + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + my $tag = $vnet->{tag}; + + raise_param_exc({ tag => "vlan tag is not allowed on simple zone"}) if defined($tag); + + if (!defined($vnet->{mac})) { + my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); + $vnet->{mac} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); + } +} + +1; + + diff --git a/src/PVE/Network/SDN/Zones/VlanPlugin.pm b/src/PVE/Network/SDN/Zones/VlanPlugin.pm new file mode 100644 index 0000000..0bb6b8a --- /dev/null +++ b/src/PVE/Network/SDN/Zones/VlanPlugin.pm @@ -0,0 +1,199 @@ +package PVE::Network::SDN::Zones::VlanPlugin; + +use strict; +use warnings; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Exception qw(raise raise_param_exc); + +use base('PVE::Network::SDN::Zones::Plugin'); + +sub type { + return 'vlan'; +} + +PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange); +sub pve_verify_sdn_vlanrange { + my ($vlanstr) = @_; + + PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096'); + + return $vlanstr; +} + +sub properties { + return { + 'bridge' => { + type => 'string', + }, + 'bridge-disable-mac-learning' => { + type => 'boolean', + description => "Disable auto mac learning.", + } + }; +} + +sub options { + + return { + nodes => { optional => 1}, + 'bridge' => { optional => 0 }, + 'bridge-disable-mac-learning' => { optional => 1 }, + mtu => { optional => 1 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + my $bridge = $plugin_config->{bridge}; + PVE::Network::SDN::Zones::Plugin::find_bridge($bridge); + + my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); + my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); + + my $tag = $vnet->{tag}; + my $alias = $vnet->{alias}; + my $mtu = $plugin_config->{mtu}; + + my $vnet_uplink = "ln_".$vnetid; + my $vnet_uplinkpeer = "pr_".$vnetid; + + my @iface_config = (); + + if($is_ovs) { + + # keep vmbrXvY for compatibility with existing network + # eth0----ovs vmbr0--(ovsintport tag)---->vnet---->vm + + @iface_config = (); + push @iface_config, "ovs_type OVSIntPort"; + push @iface_config, "ovs_bridge $bridge"; + push @iface_config, "ovs_mtu $mtu" if $mtu; + if($vnet->{vlanaware}) { + push @iface_config, "ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=$tag"; + } else { + push @iface_config, "ovs_options tag=$tag"; + } + push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink}; + + #redefine main ovs bridge, ifupdown2 will merge ovs_ports + @iface_config = (); + push @iface_config, "ovs_ports $vnet_uplink"; + push(@{$config->{$bridge}}, @iface_config); + + } elsif ($vlan_aware) { + # eth0----vlanaware bridge vmbr0--(vmbr0.X tag)---->vnet---->vm + $vnet_uplink = "$bridge.$tag"; + } else { + + # keep vmbrXvY for compatibility with existing network + # eth0<---->eth0.X----vmbr0v10------vnet---->vm + + my $bridgevlan = $bridge."v".$tag; + + my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge); + + my $bridge_ports = ""; + foreach my $bridge_iface (@bridge_ifaces) { + $bridge_ports .= " $bridge_iface.$tag"; + } + + @iface_config = (); + push @iface_config, "link-type veth"; + push @iface_config, "veth-peer-name $vnet_uplinkpeer"; + push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink}; + + @iface_config = (); + push @iface_config, "link-type veth"; + push @iface_config, "veth-peer-name $vnet_uplink"; + push(@{$config->{$vnet_uplinkpeer}}, @iface_config) if !$config->{$vnet_uplinkpeer}; + + @iface_config = (); + push @iface_config, "bridge_ports $bridge_ports $vnet_uplinkpeer"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + push(@{$config->{$bridgevlan}}, @iface_config) if !$config->{$bridgevlan}; + } + + #vnet bridge + @iface_config = (); + push @iface_config, "bridge_ports $vnet_uplink"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + if($vnet->{vlanaware}) { + push @iface_config, "bridge-vlan-aware yes"; + push @iface_config, "bridge-vids 2-4094"; + } + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "alias $alias" if $alias; + push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; + + return $config; +} + +sub status { + my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_; + + my $bridge = $plugin_config->{bridge}; + + my $err_msg = []; + if (!-d "/sys/class/net/$bridge") { + push @$err_msg, "missing $bridge"; + return $err_msg; + } + + my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge); + my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge); + + my $tag = $vnet->{tag}; + my $vnet_uplink = "ln_".$vnetid; + my $vnet_uplinkpeer = "pr_".$vnetid; + + # ifaces to check + my $ifaces = [ $vnetid, $bridge ]; + if($is_ovs) { + push @$ifaces, $vnet_uplink; + } elsif (!$vlan_aware) { + my $bridgevlan = $bridge."v".$tag; + push @$ifaces, $bridgevlan; + push @$ifaces, $vnet_uplink; + push @$ifaces, $vnet_uplinkpeer; + } + + foreach my $iface (@{$ifaces}) { + if (!$status->{$iface}->{status}) { + push @$err_msg, "missing $iface"; + } elsif ($status->{$iface}->{status} ne 'pass') { + push @$err_msg, "error iface $iface"; + } + } + return $err_msg; +} + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + my $tag = $vnet->{tag}; + + raise_param_exc({ tag => "missing vlan tag"}) if !defined($vnet->{tag}); + raise_param_exc({ tag => "vlan tag max value is 4096"}) if $vnet->{tag} > 4096; + + # verify that tag is not already defined in another vnet on same zone + foreach my $id (keys %{$vnet_cfg->{ids}}) { + next if $id eq $vnetid; + my $othervnet = $vnet_cfg->{ids}->{$id}; + my $other_tag = $othervnet->{tag}; + next if $vnet->{zone} ne $othervnet->{zone}; + raise_param_exc({ tag => "tag $tag already exist in vnet $id"}) if $other_tag && $tag eq $other_tag; + } +} + +1; + + diff --git a/src/PVE/Network/SDN/Zones/VxlanPlugin.pm b/src/PVE/Network/SDN/Zones/VxlanPlugin.pm new file mode 100644 index 0000000..c523cf7 --- /dev/null +++ b/src/PVE/Network/SDN/Zones/VxlanPlugin.pm @@ -0,0 +1,118 @@ +package PVE::Network::SDN::Zones::VxlanPlugin; + +use strict; +use warnings; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Tools qw($IPV4RE); +use PVE::INotify; +use PVE::Network::SDN::Controllers::EvpnPlugin; +use PVE::Exception qw(raise raise_param_exc); + +use base('PVE::Network::SDN::Zones::Plugin'); + +PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange); +sub pve_verify_sdn_vxlanrange { + my ($vxlanstr) = @_; + + PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216'); + + return $vxlanstr; +} + +sub type { + return 'vxlan'; +} + +sub properties { + return { + 'peers' => { + description => "peers address list.", + type => 'string', format => 'ip-list' + }, + }; +} + +sub options { + return { + nodes => { optional => 1}, + peers => { optional => 0 }, + mtu => { optional => 1 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + }; +} + +# Plugin implementation +sub generate_sdn_config { + my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_; + + my $tag = $vnet->{tag}; + my $alias = $vnet->{alias}; + my $multicastaddress = $plugin_config->{'multicast-address'}; + my @peers; + @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; + my $vxlan_iface = "vxlan_$vnetid"; + + die "missing vxlan tag" if !$tag; + + my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers); + + my $mtu = 1450; + $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu}; + $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; + + #vxlan interface + my @iface_config = (); + push @iface_config, "vxlan-id $tag"; + + for my $address (@peers) { + next if $address eq $ifaceip; + push @iface_config, "vxlan_remoteip $address"; + } + + + push @iface_config, "mtu $mtu" if $mtu; + push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface}; + + #vnet bridge + @iface_config = (); + push @iface_config, "bridge_ports $vxlan_iface"; + push @iface_config, "bridge_stp off"; + push @iface_config, "bridge_fd 0"; + if ($vnet->{vlanaware}) { + push @iface_config, "bridge-vlan-aware yes"; + push @iface_config, "bridge-vids 2-4094"; + } + push @iface_config, "mtu $mtu" if $mtu; + push @iface_config, "alias $alias" if $alias; + push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; + + return $config; +} + +sub vnet_update_hook { + my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_; + + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + my $tag = $vnet->{tag}; + + raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag); + raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216; + + # verify that tag is not already defined globally (vxlan-id are unique) + for my $id (sort keys %{$vnet_cfg->{ids}}) { + next if $id eq $vnetid; + my $othervnet = $vnet_cfg->{ids}->{$id}; + my $other_tag = $othervnet->{tag}; + my $other_zoneid = $othervnet->{zone}; + my $other_zone = $zone_cfg->{ids}->{$other_zoneid}; + next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn'; + raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag; + } +} + +1; + + diff --git a/src/test/Makefile b/src/test/Makefile new file mode 100644 index 0000000..eb59d5f --- /dev/null +++ b/src/test/Makefile @@ -0,0 +1,17 @@ +all: test + +test: test_zones test_ipams test_dns test_subnets + +test_zones: run_test_zones.pl + ./run_test_zones.pl + +test_ipams: run_test_ipams.pl + ./run_test_ipams.pl + +test_dns: run_test_dns.pl + ./run_test_dns.pl + +test_subnets: run_test_subnets.pl + ./run_test_subnets.pl + +clean: diff --git a/src/test/debug/documentation.txt b/src/test/debug/documentation.txt new file mode 100644 index 0000000..6ee8ee6 --- /dev/null +++ b/src/test/debug/documentation.txt @@ -0,0 +1,102 @@ +Here a sample of command with pvesh to manage the sdn. + + +#create a vlan transportzone +pvesh create /cluster/sdn/zones/ --zone vlanzone --type vlan --ipam pve --bridge vmbr0 +#create a vnet on vlanzone +pvesh create /cluster/sdn/vnets/ --vnet vnet100 --type vnet --zone vlanzone --tag 100 +#create a subnet on vlanzone +pvesh create /cluster/sdn/vnets/vnet100/subnets/ --type subnet --subnet 192.168.0.0/24 --gateway 192.168.0.1 + + +#create a layer2 vxlan unicast transportzone +pvesh create /cluster/sdn/zones/ --zone vxlanunicastzone --type vxlan --ipam pve --peers 192.168.0.1,192.168.0.2,192.168.0.3 + +#create an evpn controller +pvesh create /cluster/sdn/controllers/ --controller evpn1 --type evpn --peers 192.168.0.1,192.168.0.2,192.168.0.3 --asn 1234 + +#add a ebgp peer +pvesh create /cluster/sdn/controllers/ --controller bgp1 --type bgp --peers 192.168.0.253,192.168.0.254 --asn 1234 --ebgp --node pxnode1 + +#create a layer2 vxlan bgpevpn transportzone +pvesh create /cluster/sdn/zones/ --zone layer2evpnzone --type evpn --ipam pve --controller evpn1 + +#create a layer3 routable vxlan bgpevpn transportzone + exit-nodes +pvesh create /cluster/sdn/zones/ --zone layer3evpnzone --type evpn --ipam pve --controller evpn1 --vrf-vxlan 4000 --exit-nodes pxnode1,pxnode2 + + + +#create a vnet in the transportzone +pvesh create /cluster/sdn/vnets/ --vnet vnet10 --type vnet --zone vlanzone --tag 10 + +#create a vnet in the transportzone with subnets for evpn routing +pvesh create /cluster/sdn/vnets/ --vnet vnet11 --type vnet --zone layer3evpnzone --tag 11 --mac c8:1f:66:f8:62:8d +pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.0.0/24 --gateway 10.0.0.1 +pvesh create /cluster/sdn/vnets/ --vnet vnet12 --type vnet --zone layer3evpnzone --tag 12 --mac c8:1f:66:f8:62:8e +pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.1.0/24 --gateway 10.0.1.1 + +#display running configuration +pvesh get /cluster/sdn/vnets --running +pvesh get /cluster/sdn/zones --running +pvesh get /cluster/sdn/controllers --running +pvesh get /cluster/sdn/vnets/vnetX/subnets --running + + +#display pending configuration +pvesh get /cluster/sdn/vnets --pending +pvesh get /cluster/sdn/zones --pending +pvesh get /cluster/sdn/controllers --pending +pvesh get /cluster/sdn/vnets/vnetX/subnets --pending + + +#apply changes from /etc/pve/sdn.cfg.new to /etc/pve/sdn.cfg +pvesh set /cluster/sdn + + +#generate local /etc/network/interfaces.d/sdn and reload (need to be called on each node) + pvesh set /nodes//network + + +display transporzone status on all cluster nodes +#pvesh get /cluster/resources +┌────────────────────────────────────┬─────────┬───────┬───────────┬─────────┬───────┬────────┬─────────────┬────────────┬────────────┬───────────────┬──────┬───────────┬──────────────┬────────────────┐ +│ id │ type │ cpu │ disk │ hastate │ level │ maxcpu │ maxdisk │ maxmem │ mem │ node │ pool │ status │ storage │ uptime │ +│ sdn/node1/transportzone10 │ sdn │ │ │ │ │ │ │ │ │ kvmformation1 │ │ error │ │ │ +├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤ +│ sdn/node1/zone1 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │ +├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤ +│ sdn/node1/zone4 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │ +├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤ + + + + +#list all transport zones of a node + +pvesh get /nodes//sdn/zones/ + ┌─────────────────┬───────────┐ + │ sdn │ status │ + ├─────────────────┼───────────┤ + │ transportzone10 │ error │ + ├─────────────────┼───────────┤ + │ zone1 │ available │ + ├─────────────────┼───────────┤ + │ zone4 │ available │ + └─────────────────┴───────────┘ + + +#list all vnet status from a node transportzone + +pveset get /nodes//sdn/zones//content + + ┌─────────┬────────┐ + │ vnet │ status │ + ├─────────┼────────┤ + │ vnet100 │ error │ + ├─────────┼────────┤ + │ vnet101 │ error │ + └─────────┴────────┘ + + + + diff --git a/src/test/debug/generateconfig.pl b/src/test/debug/generateconfig.pl new file mode 100644 index 0000000..250db43 --- /dev/null +++ b/src/test/debug/generateconfig.pl @@ -0,0 +1,24 @@ +use strict; +use warnings; +use File::Copy; +use PVE::Cluster qw(cfs_read_file); + +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use Data::Dumper; + +PVE::Network::SDN::commit_config(); +my $network_config = PVE::Network::SDN::Zones::generate_etc_network_config(); + +PVE::Network::SDN::Zones::write_etc_network_config($network_config); +print "/etc/network/interfaces.d/sdn\n"; +print $network_config; +print "\n"; + +my $controller_config = PVE::Network::SDN::Controllers::generate_controller_config(); + +if ($controller_config) { + print Dumper($controller_config); + PVE::Network::SDN::Controllers::write_controller_config($controller_config); +} diff --git a/src/test/debug/statuscheck.pl b/src/test/debug/statuscheck.pl new file mode 100644 index 0000000..e43003b --- /dev/null +++ b/src/test/debug/statuscheck.pl @@ -0,0 +1,9 @@ +use strict; +use warnings; +use PVE::Network::SDN; +use Data::Dumper; + +my ($transport_status, $vnet_status) = PVE::Network::SDN::status(); + +print Dumper($vnet_status); +print Dumper($transport_status); diff --git a/src/test/dns/powerdns/dns_config b/src/test/dns/powerdns/dns_config new file mode 100644 index 0000000..6052366 --- /dev/null +++ b/src/test/dns/powerdns/dns_config @@ -0,0 +1,10 @@ +{ + 'ids' => { + 'powerdns' => { + 'url' => 'http://localhost:8881/api/v1/servers/localhost', + 'type' => 'powerdns', + 'key' => '1234', + 'ttl' => '3600' + }, + }, +} diff --git a/src/test/dns/powerdns/expected.add_a_multiple_record.ipv4 b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv4 new file mode 100644 index 0000000..0e5539f --- /dev/null +++ b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv4 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"},{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.add_a_multiple_record.ipv6 b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv6 new file mode 100644 index 0000000..e432e7b --- /dev/null +++ b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv6 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"},{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.add_a_record.ipv4 b/src/test/dns/powerdns/expected.add_a_record.ipv4 new file mode 100644 index 0000000..888d67f --- /dev/null +++ b/src/test/dns/powerdns/expected.add_a_record.ipv4 @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); \ No newline at end of file diff --git a/src/test/dns/powerdns/expected.add_a_record.ipv6 b/src/test/dns/powerdns/expected.add_a_record.ipv6 new file mode 100644 index 0000000..bfeeab7 --- /dev/null +++ b/src/test/dns/powerdns/expected.add_a_record.ipv6 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.add_ptr_record.ipv4 b/src/test/dns/powerdns/expected.add_ptr_record.ipv4 new file mode 100644 index 0000000..6923971 --- /dev/null +++ b/src/test/dns/powerdns/expected.add_ptr_record.ipv4 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"1.0.0.10.in-addr.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"1.0.0.10.in-addr.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.add_ptr_record.ipv6 b/src/test/dns/powerdns/expected.add_ptr_record.ipv6 new file mode 100644 index 0000000..1d8049f --- /dev/null +++ b/src/test/dns/powerdns/expected.add_ptr_record.ipv6 @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/dns/powerdns/expected.del_a_multiple_record.ipv4 b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv4 new file mode 100644 index 0000000..45d76c6 --- /dev/null +++ b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv4 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.del_a_multiple_record.ipv6 b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv6 new file mode 100644 index 0000000..9b56abd --- /dev/null +++ b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv6 @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/dns/powerdns/expected.del_a_record.ipv4 b/src/test/dns/powerdns/expected.del_a_record.ipv4 new file mode 100644 index 0000000..7c0cf45 --- /dev/null +++ b/src/test/dns/powerdns/expected.del_a_record.ipv4 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"A"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.del_a_record.ipv6 b/src/test/dns/powerdns/expected.del_a_record.ipv6 new file mode 100644 index 0000000..9494c83 --- /dev/null +++ b/src/test/dns/powerdns/expected.del_a_record.ipv6 @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"AAAA"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/dns/powerdns/expected.del_ptr_record.ipv4 b/src/test/dns/powerdns/expected.del_ptr_record.ipv4 new file mode 100644 index 0000000..120485b --- /dev/null +++ b/src/test/dns/powerdns/expected.del_ptr_record.ipv4 @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"DELETE","name":"1.0.0.10.in-addr.arpa.","records":[],"type":"PTR"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/dns/powerdns/expected.del_ptr_record.ipv6 b/src/test/dns/powerdns/expected.del_ptr_record.ipv6 new file mode 100644 index 0000000..7948e78 --- /dev/null +++ b/src/test/dns/powerdns/expected.del_ptr_record.ipv6 @@ -0,0 +1,13 @@ +bless( { + '_content' => '{"rrsets":[{"changetype":"DELETE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[],"type":"PTR"}]}', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) + }, 'HTTP::Request' ); + diff --git a/src/test/dns/powerdns/expected.verify_zone b/src/test/dns/powerdns/expected.verify_zone new file mode 100644 index 0000000..b476875 --- /dev/null +++ b/src/test/dns/powerdns/expected.verify_zone @@ -0,0 +1,12 @@ +bless( { + '_content' => '', + '_headers' => bless( { + '::std_case' => { + 'x-api-key' => 'X-API-Key' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'x-api-key' => '1234' + }, 'HTTP::Headers' ), + '_method' => 'GET', + '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com?rrsets=false')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/dns/powerdns/sdn_config b/src/test/dns/powerdns/sdn_config new file mode 100644 index 0000000..2087729 --- /dev/null +++ b/src/test/dns/powerdns/sdn_config @@ -0,0 +1,20 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type =>"simple", dns => "powerdns", reversedns => "powerdns", dnszone => "domain.com" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} diff --git a/src/test/ipams/netbox/expected.add_ip b/src/test/ipams/netbox/expected.add_ip new file mode 100644 index 0000000..ae876f2 --- /dev/null +++ b/src/test/ipams/netbox/expected.add_ip @@ -0,0 +1,9 @@ +bless( { + '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/expected.add_ip_notgateway b/src/test/ipams/netbox/expected.add_ip_notgateway new file mode 100644 index 0000000..ae876f2 --- /dev/null +++ b/src/test/ipams/netbox/expected.add_ip_notgateway @@ -0,0 +1,9 @@ +bless( { + '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/expected.add_next_freeip b/src/test/ipams/netbox/expected.add_next_freeip new file mode 100644 index 0000000..7f80f4c --- /dev/null +++ b/src/test/ipams/netbox/expected.add_next_freeip @@ -0,0 +1,9 @@ +bless( { + '_content' => '{"description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/1/available-ips/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/expected.add_subnet b/src/test/ipams/netbox/expected.add_subnet new file mode 100644 index 0000000..62ca823 --- /dev/null +++ b/src/test/ipams/netbox/expected.add_subnet @@ -0,0 +1,9 @@ +bless( { + '_content' => '{"prefix":"10.0.0.0/24"}', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/expected.del_ip b/src/test/ipams/netbox/expected.del_ip new file mode 100644 index 0000000..3c41de4 --- /dev/null +++ b/src/test/ipams/netbox/expected.del_ip @@ -0,0 +1,9 @@ +bless( { + '_content' => '', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'DELETE', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/expected.del_subnet b/src/test/ipams/netbox/expected.del_subnet new file mode 100644 index 0000000..bdadb71 --- /dev/null +++ b/src/test/ipams/netbox/expected.del_subnet @@ -0,0 +1,9 @@ +bless( { + '_content' => '{"address":"192.168.0.1/24","description":null,"dns_name":"toto"}', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/expected.update_ip b/src/test/ipams/netbox/expected.update_ip new file mode 100644 index 0000000..a1202ad --- /dev/null +++ b/src/test/ipams/netbox/expected.update_ip @@ -0,0 +1,9 @@ +bless( { + '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', + '_headers' => bless( { + 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', + 'content-type' => 'application/json; charset=UTF-8' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/netbox/ipam_config b/src/test/ipams/netbox/ipam_config new file mode 100644 index 0000000..a33be30 --- /dev/null +++ b/src/test/ipams/netbox/ipam_config @@ -0,0 +1,18 @@ +{ + 'ids' => { + 'phpipam' => { + 'url' => 'https://localhost/api/apiadmin', + 'type' => 'phpipam', + 'section' => 1, + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, + 'pve' => { + 'type' => 'pve' + }, + 'netbox' => { + 'token' => '0123456789abcdef0123456789abcdef01234567', + 'type' => 'netbox', + 'url' => 'http://localhost:8000/api' + } + }, +} diff --git a/src/test/ipams/netbox/sdn_config b/src/test/ipams/netbox/sdn_config new file mode 100644 index 0000000..c31847b --- /dev/null +++ b/src/test/ipams/netbox/sdn_config @@ -0,0 +1,20 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "netbox" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} diff --git a/src/test/ipams/phpipam/expected.add_ip b/src/test/ipams/phpipam/expected.add_ip new file mode 100644 index 0000000..50af460 --- /dev/null +++ b/src/test/ipams/phpipam/expected.add_ip @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","is_gateway":1,"mac":"da:65:8f:18:9b:6f","subnetId":1}', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/expected.add_ip_notgateway b/src/test/ipams/phpipam/expected.add_ip_notgateway new file mode 100644 index 0000000..7a91359 --- /dev/null +++ b/src/test/ipams/phpipam/expected.add_ip_notgateway @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","mac":"da:65:8f:18:9b:6f","subnetId":1}', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/expected.add_next_freeip b/src/test/ipams/phpipam/expected.add_next_freeip new file mode 100644 index 0000000..d72f94f --- /dev/null +++ b/src/test/ipams/phpipam/expected.add_next_freeip @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"description":"mydescription","hostname":"myhostname","mac":"da:65:8f:18:9b:6f"}', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/first_free/1/')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/expected.add_subnet b/src/test/ipams/phpipam/expected.add_subnet new file mode 100644 index 0000000..b10cc5a --- /dev/null +++ b/src/test/ipams/phpipam/expected.add_subnet @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"mask":"24","sectionId":1,"subnet":"10.0.0.0"}', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/subnets/')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/expected.del_ip b/src/test/ipams/phpipam/expected.del_ip new file mode 100644 index 0000000..72e83cb --- /dev/null +++ b/src/test/ipams/phpipam/expected.del_ip @@ -0,0 +1,12 @@ +bless( { + '_content' => '', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'DELETE', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/expected.del_subnet b/src/test/ipams/phpipam/expected.del_subnet new file mode 100644 index 0000000..349a34f --- /dev/null +++ b/src/test/ipams/phpipam/expected.del_subnet @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"description":null,"hostname":"toto","ip":"192.168.0.1","is_gateway":null,"subnetId":1}', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'POST', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/expected.update_ip b/src/test/ipams/phpipam/expected.update_ip new file mode 100644 index 0000000..96c219b --- /dev/null +++ b/src/test/ipams/phpipam/expected.update_ip @@ -0,0 +1,12 @@ +bless( { + '_content' => '{"description":"mydescription","hostname":"myhostname","is_gateway":1,"mac":"da:65:8f:18:9b:6f"}', + '_headers' => bless( { + '::std_case' => { + 'token' => 'Token' + }, + 'content-type' => 'application/json; charset=UTF-8', + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, 'HTTP::Headers' ), + '_method' => 'PATCH', + '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' ) + }, 'HTTP::Request' ); diff --git a/src/test/ipams/phpipam/ipam_config b/src/test/ipams/phpipam/ipam_config new file mode 100644 index 0000000..a33be30 --- /dev/null +++ b/src/test/ipams/phpipam/ipam_config @@ -0,0 +1,18 @@ +{ + 'ids' => { + 'phpipam' => { + 'url' => 'https://localhost/api/apiadmin', + 'type' => 'phpipam', + 'section' => 1, + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, + 'pve' => { + 'type' => 'pve' + }, + 'netbox' => { + 'token' => '0123456789abcdef0123456789abcdef01234567', + 'type' => 'netbox', + 'url' => 'http://localhost:8000/api' + } + }, +} diff --git a/src/test/ipams/phpipam/sdn_config b/src/test/ipams/phpipam/sdn_config new file mode 100644 index 0000000..c774807 --- /dev/null +++ b/src/test/ipams/phpipam/sdn_config @@ -0,0 +1,20 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "phpipam" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} diff --git a/src/test/run_test_dns.pl b/src/test/run_test_dns.pl new file mode 100755 index 0000000..87e011e --- /dev/null +++ b/src/test/run_test_dns.pl @@ -0,0 +1,271 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib qw(..); +use File::Slurp; +use Net::IP; + +use Test::More; +use Test::MockModule; + +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use JSON; + +use Data::Dumper qw(Dumper); +$Data::Dumper::Sortkeys = 1; + +sub read_sdn_config { + my ($file) = @_; + # Read structure back in again + open my $in, '<', $file or die $!; + my $sdn_config; + { + local $/; # slurp mode + $sdn_config = eval <$in>; + } + close $in; + + return $sdn_config; +} + + +my @plugins = read_dir( './dns/', prefix => 1 ) ; + +foreach my $path (@plugins) { + + my (undef, $dnsid) = split(/\//, $path); + my $sdn_config = read_sdn_config ("$path/sdn_config"); + + + my $pve_sdn_dns; + $pve_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns'); + $pve_sdn_dns->mock( + config => sub { + my $dns_config = read_sdn_config ("$path/dns_config"); + return $dns_config; + }, + ); + + my $sdn_module = Test::MockModule->new("PVE::Network::SDN"); + $sdn_module->mock( + config => sub { + return $sdn_config; + }, + api_request => sub { + my ($method, $url, $headers, $data) = @_; + + my $js = JSON->new; + $js->canonical(1); + + my $encoded_data = $js->encode($data) if $data; + my $req = HTTP::Request->new($method,$url, $headers, $encoded_data); + die Dumper($req); + } + ); + + + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + my $plugin_config = $dns_cfg->{ids}->{$dnsid}; + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); + + #test params; + my @ips = ("10.0.0.1", "2001:4860:4860::8888"); + my $zone = "domain.com"; + my $hostname = "myhostname"; + + foreach my $ip (@ips) { + + my $ipversion = Net::IP::ip_is_ipv6($ip) ? "ipv6" : "ipv4"; + my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A"; + my $ip2 = $type eq 'AAAA' ? '2001:4860:4860::8844' : '127.0.0.1'; + my $fqdn = $hostname.".".$zone."."; + + my $sdn_dns_plugin = Test::MockModule->new($plugin); + $sdn_dns_plugin->mock( + + get_zone_content => sub { + return undef; + }, + get_zone_rrset => sub { + return undef; + } + ); + + ## add_a_record + my $test = "add_a_record"; + my $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); + my $name = "$dnsid $test"; + + $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + ## add_ptr_record + $test = "add_ptr_record"; + $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); + $name = "$dnsid $test"; + + $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + + ## del_ptr_record + $test = "del_ptr_record"; + $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); + $name = "$dnsid $test"; + + $plugin->del_ptr_record($plugin_config, $zone, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + + ## del_a_record + + $sdn_dns_plugin->mock( + + get_zone_content => sub { + return undef; + }, + get_zone_rrset => sub { + + 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 => '3600', + records => [ $record ] }; + return $rrset; + } + ); + + $test = "del_a_record"; + $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); + $name = "$dnsid $test"; + + $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + ## del_a_multiple_record + + $sdn_dns_plugin->mock( + + get_zone_content => sub { + return undef; + }, + get_zone_rrset => sub { + + my $record = { content => $ip, + disabled => JSON::false, + name => $fqdn, + type => $type, + priority => 0 }; + + my $record2 = { content => $ip2, + disabled => JSON::false, + name => $fqdn, + type => $type, + priority => 0 }; + + my $rrset = { name => $fqdn, + type => $type, + ttl => '3600', + records => [ $record, $record2 ] }; + return $rrset; + } + ); + + $test = "del_a_multiple_record"; + $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); + $name = "$dnsid $test"; + + $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + ## add_a_multiple_record + + $sdn_dns_plugin->mock( + + get_zone_content => sub { + return undef; + }, + get_zone_rrset => sub { + + my $record2 = { content => $ip2, + disabled => JSON::false, + name => $fqdn, + type => $type, + priority => 0 }; + + my $rrset = { name => $fqdn, + type => $type, + ttl => '3600', + records => [ $record2 ] }; + return $rrset; + } + ); + + $test = "add_a_multiple_record"; + $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); + $name = "$dnsid $test"; + + $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + } + + ## verify_zone + my $test = "verify_zone"; + my $expected = Dumper read_sdn_config("$path/expected.$test"); + my $name = "$dnsid $test"; + + $plugin->verify_zone($plugin_config, $zone, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + +} + +done_testing(); + + diff --git a/src/test/run_test_ipams.pl b/src/test/run_test_ipams.pl new file mode 100755 index 0000000..27bd441 --- /dev/null +++ b/src/test/run_test_ipams.pl @@ -0,0 +1,197 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib qw(..); +use File::Slurp; + +use Test::More; +use Test::MockModule; + +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use PVE::INotify; +use JSON; + +use Data::Dumper qw(Dumper); +$Data::Dumper::Sortkeys = 1; + +sub read_sdn_config { + my ($file) = @_; + # Read structure back in again + open my $in, '<', $file or die $!; + my $sdn_config; + { + local $/; # slurp mode + $sdn_config = eval <$in>; + } + close $in; + + return $sdn_config; +} + + +#my @plugins = <./ipams/*>; +my @plugins = read_dir( './ipams/', prefix => 1 ) ; + +foreach my $path (@plugins) { + + my (undef, $ipamid) = split(/\//, $path); + my $sdn_config = read_sdn_config ("$path/sdn_config"); + + + my $pve_sdn_subnets; + $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); + $pve_sdn_subnets->mock( + config => sub { + return $sdn_config->{subnets}; + }, + ); + + my $pve_sdn_ipam; + $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Ipams'); + $pve_sdn_subnets->mock( + config => sub { + my $ipam_config = read_sdn_config ("$path/ipam_config"); + return $ipam_config; + }, + ); + + my $sdn_module = Test::MockModule->new("PVE::Network::SDN"); + $sdn_module->mock( + config => sub { + return $sdn_config; + }, + api_request => sub { + my ($method, $url, $headers, $data) = @_; + + my $js = JSON->new; + $js->canonical(1); + + my $encoded_data = $js->encode($data) if $data; + my $req = HTTP::Request->new($method,$url, $headers, $encoded_data); + die Dumper($req); + } + ); + + + + #test params; + my $subnetid = "myzone-10.0.0.0-24"; + my $ip = "10.0.0.1"; + my $hostname = "myhostname"; + my $mac = "da:65:8f:18:9b:6f"; + my $description = "mydescription"; + my $is_gateway = 1; + + + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); + + 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 $sdn_ipam_plugin = Test::MockModule->new($plugin); + $sdn_ipam_plugin->mock( + get_prefix_id => sub { + return 1; + }, + get_ip_id => sub { + return 1; + }, + is_ip_gateway => sub { + return 1; + } + ); + + ## add_ip + my $test = "add_ip"; + my $expected = Dumper read_sdn_config("$path/expected.$test"); + my $name = "$ipamid $test"; + + $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + ## add_next_freeip + $test = "add_next_freeip"; + $expected = Dumper read_sdn_config("$path/expected.$test"); + $name = "$ipamid $test"; + + $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + + ## del_ip + $test = "del_ip"; + $expected = Dumper read_sdn_config("$path/expected.$test"); + $name = "$ipamid $test"; + + $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + ## update_ip + $test = "update_ip"; + $expected = Dumper read_sdn_config("$path/expected.$test"); + $name = "$ipamid $test"; + $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + ## add_ip_notgateway + $is_gateway = undef; + $test = "add_ip_notgateway"; + $expected = Dumper read_sdn_config("$path/expected.$test"); + $name = "$ipamid $test"; + + $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + + $sdn_ipam_plugin->mock( + get_prefix_id => sub { + return undef; + }, + ); + + ## add_subnet + $test = "add_subnet"; + $expected = Dumper read_sdn_config("$path/expected.$test"); + $name = "$ipamid $test"; + + $plugin->add_subnet($plugin_config, $subnetid, $subnet, 1); + + if ($@) { + is ($@, $expected, $name); + } else { + fail($name); + } + +} + +done_testing(); + + diff --git a/src/test/run_test_subnets.pl b/src/test/run_test_subnets.pl new file mode 100755 index 0000000..f6564e1 --- /dev/null +++ b/src/test/run_test_subnets.pl @@ -0,0 +1,305 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib qw(..); +use File::Slurp; + +use Test::More; +use Test::MockModule; + +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use PVE::INotify; +use JSON; + +use Data::Dumper qw(Dumper); +$Data::Dumper::Sortkeys = 1; + +sub read_sdn_config { + my ($file) = @_; + # Read structure back in again + open my $in, '<', $file or die $!; + my $sdn_config; + { + local $/; # slurp mode + $sdn_config = eval <$in>; + } + close $in; + + return $sdn_config; +} + + +my @plugins = read_dir( './subnets/', prefix => 1 ) ; + +foreach my $path (@plugins) { + + my (undef, $testid) = split(/\//, $path); + + print "test: $testid\n"; + my $sdn_config = read_sdn_config ("$path/sdn_config"); + + + my $pve_sdn_subnets; + $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); + $pve_sdn_subnets->mock( + config => sub { + return $sdn_config->{subnets}; + }, + verify_dns_zone => sub { + return; + }, + add_dns_record => sub { + return; + } + ); + + + my $js = JSON->new; + $js->canonical(1); + + + #test params; + my $subnets = $sdn_config->{subnets}->{ids}; + my $subnetid = (keys %{$subnets})[0]; + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); + + my $subnet_cidr = $subnet->{cidr}; + my $iplist = NetAddr::IP->new($subnet_cidr); + $iplist++ if Net::IP::ip_is_ipv4($iplist->canon()); #skip network address for ipv4 + my $ip = $iplist->canon(); + $iplist++; + my $ipnextfree = $iplist->canon(); + $iplist++; + my $ip2 = $iplist->canon(); + + my $ip3 = undef; + my $hostname = "myhostname"; + my $mac = "da:65:8f:18:9b:6f"; + my $description = "mydescription"; + my $is_gateway = 1; + my $ipamdb = {}; + + my $zone = $sdn_config->{zones}->{ids}->{"myzone"}; + my $ipam = $zone->{ipam}; + + my $plugin; + my $sdn_ipam_plugin; + if($ipam) { + $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam); + $sdn_ipam_plugin = Test::MockModule->new($plugin); + $sdn_ipam_plugin->mock( + read_db => sub { + return $ipamdb; + }, + write_db => sub { + my ($cfg) = @_; + $ipamdb = $cfg; + } + ); + } + + my $pve_sdn_ipams; + $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams'); + $pve_sdn_ipams->mock( + config => sub { + my $ipam_config = read_sdn_config ("$path/ipam_config"); + return $ipam_config; + }, + ); + + ## add_subnet + my $test = "add_subnet $subnetid"; + my $name = "$testid $test"; + my $result = undef; + my $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{}}}}}}'; + + eval { + PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet); + + }; + + if ($@) { + fail("$name : $@"); + } elsif($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } else { + is (undef, undef, $name); + } + + ## add_ip + $test = "add_ip $ip"; + $name = "$testid $test"; + $result = undef; + $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1}}}}}}}'; + + eval { + PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway); + }; + + if ($@) { + fail("$name : $@"); + } elsif($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } else { + is (undef, undef, $name); + } + + if($ipam) { + ## add_already_exist_ip + $test = "add_already_exist_ip $ip"; + $name = "$testid $test"; + + eval { + PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description); + }; + + if ($@) { + is (undef, undef, $name); + } else { + fail("$name : $@"); + } + } + + ## add_second_ip + $test = "add_second_ip $ip2"; + $name = "$testid $test"; + $result = undef; + $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ip2.'":{}}}}}}}'; + + eval { + PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip2, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } elsif($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } else { + is (undef, undef, $name); + } + + ## add_next_free + $test = "find_next_freeip ($ipnextfree)"; + $name = "$testid $test"; + $result = undef; + $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}'; + + eval { + $ip3 = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } elsif($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } + + ## del_ip + $test = "del_ip $ip"; + $name = "$testid $test"; + $result = undef; + $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}'; + + eval { + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname); + }; + + if ($@) { + fail("$name : $@"); + } elsif($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } else { + is (undef, undef, $name); + } + + if($ipam){ + ## del_subnet_not_empty + $test = "del_subnet_not_empty $subnetid"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet); + }; + + if ($@) { + is ($result, $expected, $name); + } else { + fail("$name : $@"); + } + } + + + ## add_ip_rollback_failing_dns + $test = "add_ip_rollback_failing_dns"; + + $pve_sdn_subnets->mock( + config => sub { + return $sdn_config->{subnets}; + }, + verify_dns_zone => sub { + return; + }, + add_dns_record => sub { + die "error add dns record"; + return; + } + ); + + $name = "$testid $test"; + $result = undef; + $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}'; + + eval { + PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description); + }; + + if ($@) { + if($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } else { + is (undef, undef, $name); + } + } else { + fail("$name : $@"); + } + + + ## del_empty_subnet + $test = "del_empty_subnet"; + $name = "$testid $test"; + $result = undef; + $expected = '{"zones":{"myzone":{"subnets":{}}}}'; + + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip2, $hostname); + PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip3, $hostname); + + eval { + PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet); + }; + + if ($@) { + fail("$name : $@"); + } elsif($ipam) { + $result = $js->encode($plugin->read_db()); + is ($result, $expected, $name); + } else { + is (undef, undef, $name); + } + +} + +done_testing(); + + diff --git a/src/test/run_test_vnets.pl b/src/test/run_test_vnets.pl new file mode 100755 index 0000000..5aeb676 --- /dev/null +++ b/src/test/run_test_vnets.pl @@ -0,0 +1,355 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib qw(..); +use File::Slurp; +use NetAddr::IP qw(:lower); + +use Test::More; +use Test::MockModule; + +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use PVE::INotify; +use JSON; + +use Data::Dumper qw(Dumper); +$Data::Dumper::Sortkeys = 1; + +sub read_sdn_config { + my ($file) = @_; + # Read structure back in again + open my $in, '<', $file or die $!; + my $sdn_config; + { + local $/; # slurp mode + $sdn_config = eval <$in>; + } + close $in; + return $sdn_config; +} + + +my @plugins = read_dir( './vnets/', prefix => 1 ) ; + +foreach my $path (@plugins) { + + my (undef, $testid) = split(/\//, $path); + + print "test: $testid\n"; + my $sdn_config = read_sdn_config ("$path/sdn_config"); + + my $pve_sdn_zones; + $pve_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones'); + $pve_sdn_zones->mock( + config => sub { + return $sdn_config->{zones}; + }, + ); + + my $pve_sdn_vnets; + $pve_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets'); + $pve_sdn_vnets->mock( + config => sub { + return $sdn_config->{vnets}; + }, + ); + + my $pve_sdn_subnets; + $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); + $pve_sdn_subnets->mock( + config => sub { + return $sdn_config->{subnets}; + }, + verify_dns_zone => sub { + return; + }, + add_dns_record => sub { + return; + } + ); + + my $js = JSON->new; + $js->canonical(1); + + #test params; + #test params; + my $subnets = $sdn_config->{subnets}->{ids}; + + my $subnetid = (sort keys %{$subnets})[0]; + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); + my $subnet_cidr = $subnet->{cidr}; + my $iplist = NetAddr::IP->new($subnet_cidr); + my $mask = $iplist->masklen(); + my $ipversion = undef; + + if (Net::IP::ip_is_ipv4($iplist->canon())){ + $iplist++; #skip network address for ipv4 + $ipversion = 4; + } else { + $ipversion = 6; + } + + my $cidr1 = $iplist->canon()."/$mask"; + $iplist++; + my $cidr2 = $iplist->canon()."/$mask"; + my $cidr_outofrange = '8.8.8.8/8'; + + my $subnetid2 = (sort keys %{$subnets})[1]; + my $subnet2 = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid2, 1); + my $subnet2_cidr = $subnet2->{cidr}; + my $iplist2 = NetAddr::IP->new($subnet2_cidr); + $iplist2++; + my $cidr3 = $iplist2->canon()."/$mask"; + $iplist2++; + my $cidr4 = $iplist2->canon()."/$mask"; + + my $hostname = "myhostname"; + my $mac = "da:65:8f:18:9b:6f"; + my $description = "mydescription"; + my $ipamdb = read_sdn_config ("$path/ipam.db"); + + my $zone = $sdn_config->{zones}->{ids}->{"myzone"}; + my $ipam = $zone->{ipam}; + + my $plugin; + my $sdn_ipam_plugin; + if($ipam) { + $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam); + $sdn_ipam_plugin = Test::MockModule->new($plugin); + $sdn_ipam_plugin->mock( + read_db => sub { + return $ipamdb; + }, + write_db => sub { + my ($cfg) = @_; + $ipamdb = $cfg; + } + ); + } + + my $pve_sdn_ipams; + $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams'); + $pve_sdn_ipams->mock( + config => sub { + my $ipam_config = read_sdn_config ("$path/ipam_config"); + return $ipam_config; + }, + ); + + my $vnetid = "myvnet"; + + ## add_ip + my $test = "add_cidr $cidr1"; + my $name = "$testid $test"; + my $result = undef; + my $expected = ''; + + eval { + PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + ## add_ip + $test = "add_already_exist_cidr $cidr1"; + $name = "$testid $test"; + $result = undef; + $expected = ''; + + eval { + PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description); + }; + + if ($@) { + is (undef, undef, $name); + } elsif($ipam) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + ## add_ip + $test = "add_cidr $cidr2"; + $name = "$testid $test"; + $result = undef; + $expected = ''; + + eval { + PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr2, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + ## add_ip + $test = "add_ip_out_of_range_subnets $cidr_outofrange"; + $name = "$testid $test"; + $result = undef; + $expected = ''; + + eval { + PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr_outofrange, $hostname, $mac, $description); + }; + + if ($@) { + is (undef, undef, $name); + } else { + fail("$name : $@"); + } + + ## add_ip + $test = "add_cidr $cidr4"; + $name = "$testid $test"; + $result = undef; + $expected = ''; + + eval { + PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr4, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + + $test = "find_next_free_cidr_in_second_subnet ($cidr3)"; + $name = "$testid $test"; + $result = undef; + $expected = $ipam ? $cidr3 : undef; + + eval { + $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion); + }; + + if ($@) { + fail("$name : $@"); + } else { + is ($result, $expected, $name); + } + + + $test = "del_cidr $cidr1"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + $test = "del_cidr $cidr3"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr3, $hostname); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + $test = "del_cidr not exist $cidr1"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname); + }; + + if ($@) { + is (undef, undef, $name); + } elsif($ipam) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + $test = "del_cidr outofrange $cidr_outofrange"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr_outofrange, $hostname); + }; + + if ($@) { + is (undef, undef, $name); + } else { + fail("$name : $@"); + } + + $test = "find_next_free_cidr_in_first_subnet ($cidr1)"; + $name = "$testid $test"; + $result = undef; + $expected = $ipam ? $cidr1 : undef; + + eval { + $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion); + }; + + if ($@) { + fail("$name : $@"); + } else { + is ($result, $expected, $name); + } + + $test = "update_cidr $cidr1"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + + $test = "update_cidr deleted $cidr3"; + $name = "$testid $test"; + $result = undef; + $expected = undef; + + eval { + $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description); + }; + + if ($@) { + fail("$name : $@"); + } else { + is (undef, undef, $name); + } + +} + +done_testing(); + + diff --git a/src/test/run_test_zones.pl b/src/test/run_test_zones.pl new file mode 100755 index 0000000..12e017a --- /dev/null +++ b/src/test/run_test_zones.pl @@ -0,0 +1,123 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib qw(..); +use File::Slurp; + +use Test::More; +use Test::MockModule; + +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use PVE::INotify; + +sub read_sdn_config { + my ($file) = @_; + + # Read structure back in again + open my $in, '<', $file or die $!; + my $sdn_config; + { + local $/; # slurp mode + $sdn_config = eval <$in>; + } + close $in; + + return $sdn_config; +} + + +my @tests = grep { -d } glob './zones/*/*'; + +foreach my $test (@tests) { + + my $sdn_config = read_sdn_config ("./$test/sdn_config"); + + open my $fh1, '<', "./$test/interfaces" or die "can't read interfaces file"; + my $interfaces_config = PVE::INotify::__read_etc_network_interfaces($fh1, undef, undef); + close $fh1; + + my $pve_common_inotify; + $pve_common_inotify = Test::MockModule->new('PVE::INotify'); + $pve_common_inotify->mock( + nodename => sub { + return 'localhost'; + }, + read_file => sub { + return $interfaces_config; + }, + ); + + my $pve_sdn_subnets; + $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); + $pve_sdn_subnets->mock( + config => sub { + return $sdn_config->{subnets}; + }, + ); + + my $pve_sdn_zones_plugin; + $pve_sdn_zones_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin'); + $pve_sdn_zones_plugin->mock( + get_local_route_ip => sub { + my $outiface = "vmbr0"; + my $outip = $interfaces_config->{ifaces}->{$outiface}->{address}; + return ($outip, $outiface); + }, + is_vlanaware => sub { + return $interfaces_config->{ifaces}->{vmbr0}->{'bridge_vlan_aware'}; + }, + is_ovs => sub { + return 1 if $interfaces_config->{ifaces}->{vmbr0}->{'type'} eq 'OVSBridge'; + }, + get_bridge_ifaces => sub { + return ('eth0'); + }, + find_bridge => sub { + return; + } + ); + + my $sdn_module = Test::MockModule->new("PVE::Network::SDN"); + $sdn_module->mock( + running_config => sub { + return $sdn_config; + }, + ); + + my $name = $test; + my $expected = read_file("./$test/expected_sdn_interfaces"); + + my $result = ""; + eval { + $result = PVE::Network::SDN::Zones::generate_etc_network_config(); + }; + + if (my $err = $@) { + fail($name); + } else { + is ($result, $expected, $name); + } + + if ($sdn_config->{controllers}) { + my $expected = read_file("./$test/expected_controller_config"); + my $controller_rawconfig = ""; + + eval { + my $config = PVE::Network::SDN::Controllers::generate_controller_config(); + $controller_rawconfig = PVE::Network::SDN::Controllers::generate_controller_rawconfig($config); + }; + if (my $err = $@) { + fail($name); + } else { + is ($controller_rawconfig, $expected, $name); + } + } +} + +done_testing(); + + diff --git a/src/test/subnets/ipv4/ipam_config b/src/test/subnets/ipv4/ipam_config new file mode 100644 index 0000000..a33be30 --- /dev/null +++ b/src/test/subnets/ipv4/ipam_config @@ -0,0 +1,18 @@ +{ + 'ids' => { + 'phpipam' => { + 'url' => 'https://localhost/api/apiadmin', + 'type' => 'phpipam', + 'section' => 1, + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, + 'pve' => { + 'type' => 'pve' + }, + 'netbox' => { + 'token' => '0123456789abcdef0123456789abcdef01234567', + 'type' => 'netbox', + 'url' => 'http://localhost:8000/api' + } + }, +} diff --git a/src/test/subnets/ipv4/sdn_config b/src/test/subnets/ipv4/sdn_config new file mode 100644 index 0000000..72697d4 --- /dev/null +++ b/src/test/subnets/ipv4/sdn_config @@ -0,0 +1,20 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type =>"simple" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} diff --git a/src/test/subnets/ipv6/ipam_config b/src/test/subnets/ipv6/ipam_config new file mode 100644 index 0000000..a33be30 --- /dev/null +++ b/src/test/subnets/ipv6/ipam_config @@ -0,0 +1,18 @@ +{ + 'ids' => { + 'phpipam' => { + 'url' => 'https://localhost/api/apiadmin', + 'type' => 'phpipam', + 'section' => 1, + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, + 'pve' => { + 'type' => 'pve' + }, + 'netbox' => { + 'token' => '0123456789abcdef0123456789abcdef01234567', + 'type' => 'netbox', + 'url' => 'http://localhost:8000/api' + } + }, +} diff --git a/src/test/subnets/ipv6/sdn_config b/src/test/subnets/ipv6/sdn_config new file mode 100644 index 0000000..618f234 --- /dev/null +++ b/src/test/subnets/ipv6/sdn_config @@ -0,0 +1,20 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type =>"simple" } }, + }, + + subnets => { + ids => { 'myzone-2a0a:1580:2000::-56' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} diff --git a/src/test/subnets/noipam/ipam_config b/src/test/subnets/noipam/ipam_config new file mode 100644 index 0000000..a33be30 --- /dev/null +++ b/src/test/subnets/noipam/ipam_config @@ -0,0 +1,18 @@ +{ + 'ids' => { + 'phpipam' => { + 'url' => 'https://localhost/api/apiadmin', + 'type' => 'phpipam', + 'section' => 1, + 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' + }, + 'pve' => { + 'type' => 'pve' + }, + 'netbox' => { + 'token' => '0123456789abcdef0123456789abcdef01234567', + 'type' => 'netbox', + 'url' => 'http://localhost:8000/api' + } + }, +} diff --git a/src/test/subnets/noipam/sdn_config b/src/test/subnets/noipam/sdn_config new file mode 100644 index 0000000..55107d6 --- /dev/null +++ b/src/test/subnets/noipam/sdn_config @@ -0,0 +1,20 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { type =>"simple" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} diff --git a/src/test/vnets/ipv4/ipam.db b/src/test/vnets/ipv4/ipam.db new file mode 100644 index 0000000..ef3fa93 --- /dev/null +++ b/src/test/vnets/ipv4/ipam.db @@ -0,0 +1,17 @@ +{ + "zones" => { + "myzone" => { + "subnets" => { + "192.168.0.0/30" => { + "ips" =>{ + } + }, + "192.168.1.0/30" => { + "ips" =>{ + } + }, + } + } + } +} + diff --git a/src/test/vnets/ipv4/ipam_config b/src/test/vnets/ipv4/ipam_config new file mode 100644 index 0000000..f5f36ad --- /dev/null +++ b/src/test/vnets/ipv4/ipam_config @@ -0,0 +1,7 @@ +{ + 'ids' => { + 'pve' => { + 'type' => 'pve' + }, + }, +} diff --git a/src/test/vnets/ipv4/sdn_config b/src/test/vnets/ipv4/sdn_config new file mode 100644 index 0000000..ee11fd1 --- /dev/null +++ b/src/test/vnets/ipv4/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type =>"simple" } }, + }, + + subnets => { + ids => { + 'myzone-192.168.0.0-30' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + 'myzone-192.168.1.0-30' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + } + + } +} diff --git a/src/test/vnets/ipv4noipam/ipam.db b/src/test/vnets/ipv4noipam/ipam.db new file mode 100644 index 0000000..ef3fa93 --- /dev/null +++ b/src/test/vnets/ipv4noipam/ipam.db @@ -0,0 +1,17 @@ +{ + "zones" => { + "myzone" => { + "subnets" => { + "192.168.0.0/30" => { + "ips" =>{ + } + }, + "192.168.1.0/30" => { + "ips" =>{ + } + }, + } + } + } +} + diff --git a/src/test/vnets/ipv4noipam/ipam_config b/src/test/vnets/ipv4noipam/ipam_config new file mode 100644 index 0000000..f5f36ad --- /dev/null +++ b/src/test/vnets/ipv4noipam/ipam_config @@ -0,0 +1,7 @@ +{ + 'ids' => { + 'pve' => { + 'type' => 'pve' + }, + }, +} diff --git a/src/test/vnets/ipv4noipam/sdn_config b/src/test/vnets/ipv4noipam/sdn_config new file mode 100644 index 0000000..470c1ae --- /dev/null +++ b/src/test/vnets/ipv4noipam/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { type =>"simple" } }, + }, + + subnets => { + ids => { + 'myzone-192.168.0.0-30' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + 'myzone-192.168.1.0-30' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + } + + } +} diff --git a/src/test/vnets/ipv6/ipam.db b/src/test/vnets/ipv6/ipam.db new file mode 100644 index 0000000..d3f2ce9 --- /dev/null +++ b/src/test/vnets/ipv6/ipam.db @@ -0,0 +1,16 @@ +{ + "zones" => { + "myzone" => { + "subnets" => { + "2001:db8:85a3::8a2e:370:7334/127" => { + "ips" =>{ + } + }, + "2001:db8:85a3::8a2e:371:7334/127" => { + "ips" =>{ + } + }, + } + } + } +} diff --git a/src/test/vnets/ipv6/ipam_config b/src/test/vnets/ipv6/ipam_config new file mode 100644 index 0000000..f5f36ad --- /dev/null +++ b/src/test/vnets/ipv6/ipam_config @@ -0,0 +1,7 @@ +{ + 'ids' => { + 'pve' => { + 'type' => 'pve' + }, + }, +} diff --git a/src/test/vnets/ipv6/sdn_config b/src/test/vnets/ipv6/sdn_config new file mode 100644 index 0000000..231ca8a --- /dev/null +++ b/src/test/vnets/ipv6/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type =>"simple" } }, + }, + + subnets => { + ids => { + 'myzone-2001:db8:85a3::8a2e:370:7334-127' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + 'myzone-2001:db8:85a3::8a2e:371:7334-127' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + } + + } +} diff --git a/src/test/zones/evpn/advertise_subnets/expected_controller_config b/src/test/zones/evpn/advertise_subnets/expected_controller_config new file mode 100644 index 0000000..82b06b4 --- /dev/null +++ b/src/test/zones/evpn/advertise_subnets/expected_controller_config @@ -0,0 +1,54 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + advertise ipv6 unicast + exit-address-family +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces b/src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces new file mode 100644 index 0000000..9d1c64c --- /dev/null +++ b/src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces @@ -0,0 +1,42 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/advertise_subnets/interfaces b/src/test/zones/evpn/advertise_subnets/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/advertise_subnets/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/advertise_subnets/sdn_config b/src/test/zones/evpn/advertise_subnets/sdn_config new file mode 100644 index 0000000..76f16a1 --- /dev/null +++ b/src/test/zones/evpn/advertise_subnets/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'advertise-subnets' => 1 } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config b/src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config new file mode 100644 index 0000000..bd7830a --- /dev/null +++ b/src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config @@ -0,0 +1,41 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces b/src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces new file mode 100644 index 0000000..bbde906 --- /dev/null +++ b/src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces @@ -0,0 +1,40 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + mtu 1450 diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/interfaces b/src/test/zones/evpn/disable_arp_nd_suppression/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/disable_arp_nd_suppression/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/sdn_config b/src/test/zones/evpn/disable_arp_nd_suppression/sdn_config new file mode 100644 index 0000000..199596b --- /dev/null +++ b/src/test/zones/evpn/disable_arp_nd_suppression/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'disable-arp-nd-suppression' => 1 } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/evpn/ebgp/expected_controller_config b/src/test/zones/evpn/ebgp/expected_controller_config new file mode 100644 index 0000000..ccc0b28 --- /dev/null +++ b/src/test/zones/evpn/ebgp/expected_controller_config @@ -0,0 +1,58 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65001 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as external + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + neighbor BGP peer-group + neighbor BGP remote-as external + neighbor BGP bfd + neighbor BGP ebgp-multihop 3 + neighbor 192.168.0.252 peer-group BGP + neighbor 192.168.0.253 peer-group BGP + ! + address-family ipv4 unicast + neighbor BGP activate + neighbor BGP soft-reconfiguration inbound + exit-address-family + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + autort as 65000 + exit-address-family +exit +! +router bgp 65001 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family l2vpn evpn + route-target import 65000:1000 + route-target export 65000:1000 + exit-address-family +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/ebgp/expected_sdn_interfaces b/src/test/zones/evpn/ebgp/expected_sdn_interfaces new file mode 100644 index 0000000..4cf13e0 --- /dev/null +++ b/src/test/zones/evpn/ebgp/expected_sdn_interfaces @@ -0,0 +1,41 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/ebgp/interfaces b/src/test/zones/evpn/ebgp/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/ebgp/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/ebgp/sdn_config b/src/test/zones/evpn/ebgp/sdn_config new file mode 100644 index 0000000..6e9d116 --- /dev/null +++ b/src/test/zones/evpn/ebgp/sdn_config @@ -0,0 +1,50 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { + tag => "100", + type => "vnet", + zone => "myzone", + }, + }, + }, + + zones => { + ids => { + myzone => { + ipam => "pve", + type => "evpn", + controller => "evpnctl", + 'vrf-vxlan' => 1000, + }, + }, + }, + controllers => { + ids => { + evpnctl => { + type => "evpn", + 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', + asn => "65000", + }, + localhost => { + type => "bgp", + 'peers' => '192.168.0.252,192.168.0.253', + ebgp => "1", + 'ebgp-multihop' => '3', + asn => "65001", + node => "localhost", + }, + }, + }, + + subnets => { + ids => { + 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + }, + }, + }, +} diff --git a/src/test/zones/evpn/ebgp_loopback/expected_controller_config b/src/test/zones/evpn/ebgp_loopback/expected_controller_config new file mode 100644 index 0000000..548d532 --- /dev/null +++ b/src/test/zones/evpn/ebgp_loopback/expected_controller_config @@ -0,0 +1,69 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +ip protocol bgp route-map correct_src +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65001 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as external + neighbor VTEP bfd + neighbor VTEP ebgp-multihop 10 + neighbor VTEP update-source dummy1 + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + bgp disable-ebgp-connected-route-check + neighbor BGP peer-group + neighbor BGP remote-as external + neighbor BGP bfd + neighbor 172.16.0.254 peer-group BGP + neighbor 172.17.0.254 peer-group BGP + ! + address-family ipv4 unicast + network 192.168.0.1/32 + neighbor BGP activate + neighbor BGP soft-reconfiguration inbound + exit-address-family + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + autort as 65000 + exit-address-family +exit +! +router bgp 65001 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family l2vpn evpn + route-target import 65000:1000 + route-target export 65000:1000 + exit-address-family +exit +! +ip prefix-list loopbacks_ips seq 10 permit 0.0.0.0/0 le 32 +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +route-map correct_src permit 1 + match ip address prefix-list loopbacks_ips + set src 192.168.0.1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces b/src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces new file mode 100644 index 0000000..4cf13e0 --- /dev/null +++ b/src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces @@ -0,0 +1,41 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/ebgp_loopback/interfaces b/src/test/zones/evpn/ebgp_loopback/interfaces new file mode 100644 index 0000000..f6bc352 --- /dev/null +++ b/src/test/zones/evpn/ebgp_loopback/interfaces @@ -0,0 +1,13 @@ +auto eth0 +iface eth0 inet static + address 172.16.0.1/24 + +auto eth1 +iface eth1 inet static + address 172.17.0.1/24 + +auto dummy1 +iface dummy1 inet static + address 192.168.0.1/32 + link-type dummy + diff --git a/src/test/zones/evpn/ebgp_loopback/sdn_config b/src/test/zones/evpn/ebgp_loopback/sdn_config new file mode 100644 index 0000000..c8bc2e0 --- /dev/null +++ b/src/test/zones/evpn/ebgp_loopback/sdn_config @@ -0,0 +1,29 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000 } }, + }, + controllers => { + ids => { + evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" }, + localhost => { type => "bgp", 'peers' => '172.16.0.254,172.17.0.254', ebgp => "1", asn => "65001", loopback => 'dummy1', node => "localhost" }, + }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/evpn/exitnode/expected_controller_config b/src/test/zones/evpn/exitnode/expected_controller_config new file mode 100644 index 0000000..48830a3 --- /dev/null +++ b/src/test/zones/evpn/exitnode/expected_controller_config @@ -0,0 +1,66 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family ipv4 unicast + import vrf vrf_myzone + exit-address-family + ! + address-family ipv6 unicast + import vrf vrf_myzone + exit-address-family + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + exit-address-family + ! + address-family l2vpn evpn + default-originate ipv4 + default-originate ipv6 + exit-address-family +exit +! +route-map MAP_VTEP_IN deny 1 + match evpn route-type prefix +exit +! +route-map MAP_VTEP_IN permit 2 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/exitnode/expected_sdn_interfaces b/src/test/zones/evpn/exitnode/expected_sdn_interfaces new file mode 100644 index 0000000..5ab3084 --- /dev/null +++ b/src/test/zones/evpn/exitnode/expected_sdn_interfaces @@ -0,0 +1,41 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/exitnode/interfaces b/src/test/zones/evpn/exitnode/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/exitnode/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/exitnode/sdn_config b/src/test/zones/evpn/exitnode/sdn_config new file mode 100644 index 0000000..fd81817 --- /dev/null +++ b/src/test/zones/evpn/exitnode/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/evpn/exitnode_local_routing/expected_controller_config b/src/test/zones/evpn/exitnode_local_routing/expected_controller_config new file mode 100644 index 0000000..f671b63 --- /dev/null +++ b/src/test/zones/evpn/exitnode_local_routing/expected_controller_config @@ -0,0 +1,51 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +ip route 10.0.0.0/24 10.255.255.2 xvrf_myzone +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family l2vpn evpn + default-originate ipv4 + default-originate ipv6 + exit-address-family +exit +! +route-map MAP_VTEP_IN deny 1 + match evpn route-type prefix +exit +! +route-map MAP_VTEP_IN permit 2 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces new file mode 100644 index 0000000..301f5b3 --- /dev/null +++ b/src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces @@ -0,0 +1,56 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto xvrf_myzone +iface xvrf_myzone + link-type veth + address 10.255.255.1/30 + veth-peer-name xvrfp_myzone + mtu 1500 + +auto xvrfp_myzone +iface xvrfp_myzone + link-type veth + address 10.255.255.2/30 + veth-peer-name xvrf_myzone + vrf vrf_myzone + mtu 1500 diff --git a/src/test/zones/evpn/exitnode_local_routing/interfaces b/src/test/zones/evpn/exitnode_local_routing/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/exitnode_local_routing/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/exitnode_local_routing/sdn_config b/src/test/zones/evpn/exitnode_local_routing/sdn_config new file mode 100644 index 0000000..f5f7ca1 --- /dev/null +++ b/src/test/zones/evpn/exitnode_local_routing/sdn_config @@ -0,0 +1,27 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 }, 'exitnodes-local-routing' => 1 }, + }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + }, + } + } +} + + diff --git a/src/test/zones/evpn/exitnode_primary/expected_controller_config b/src/test/zones/evpn/exitnode_primary/expected_controller_config new file mode 100644 index 0000000..e45b22c --- /dev/null +++ b/src/test/zones/evpn/exitnode_primary/expected_controller_config @@ -0,0 +1,68 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family ipv4 unicast + import vrf vrf_myzone + exit-address-family + ! + address-family ipv6 unicast + import vrf vrf_myzone + exit-address-family + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + exit-address-family + ! + address-family l2vpn evpn + default-originate ipv4 + default-originate ipv6 + exit-address-family +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 + match evpn vni 1000 + match evpn route-type prefix + set metric 200 +exit +! +route-map MAP_VTEP_OUT permit 2 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces new file mode 100644 index 0000000..5ab3084 --- /dev/null +++ b/src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces @@ -0,0 +1,41 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/exitnode_primary/interfaces b/src/test/zones/evpn/exitnode_primary/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/exitnode_primary/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/exitnode_primary/sdn_config b/src/test/zones/evpn/exitnode_primary/sdn_config new file mode 100644 index 0000000..bfeafc5 --- /dev/null +++ b/src/test/zones/evpn/exitnode_primary/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'exitnodes-primary' => "othernode", exitnodes => { 'localhost' => 1 } } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/evpn/exitnode_snat/expected_controller_config b/src/test/zones/evpn/exitnode_snat/expected_controller_config new file mode 100644 index 0000000..48830a3 --- /dev/null +++ b/src/test/zones/evpn/exitnode_snat/expected_controller_config @@ -0,0 +1,66 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family ipv4 unicast + import vrf vrf_myzone + exit-address-family + ! + address-family ipv6 unicast + import vrf vrf_myzone + exit-address-family + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + exit-address-family + ! + address-family l2vpn evpn + default-originate ipv4 + default-originate ipv6 + exit-address-family +exit +! +route-map MAP_VTEP_IN deny 1 + match evpn route-type prefix +exit +! +route-map MAP_VTEP_IN permit 2 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces new file mode 100644 index 0000000..47df77a --- /dev/null +++ b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces @@ -0,0 +1,68 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto myvnet2 +iface myvnet2 + address 2a08:2142:302:3::1/64 + post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 + bridge_ports vxlan_myvnet2 + bridge_stp off + bridge_fd 0 + mtu 1450 + ip6-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet2 +iface vxlan_myvnet2 + vxlan-id 200 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/exitnode_snat/interfaces b/src/test/zones/evpn/exitnode_snat/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/exitnode_snat/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/exitnode_snat/sdn_config b/src/test/zones/evpn/exitnode_snat/sdn_config new file mode 100644 index 0000000..35cdf5d --- /dev/null +++ b/src/test/zones/evpn/exitnode_snat/sdn_config @@ -0,0 +1,35 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + myvnet2 => { tag => "200", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { + 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + 'snat' => 1 + }, + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet2', + 'gateway' => '2a08:2142:302:3::1', + 'snat' => 1 + } + } + } +} + + diff --git a/src/test/zones/evpn/ipv4/expected_controller_config b/src/test/zones/evpn/ipv4/expected_controller_config new file mode 100644 index 0000000..bd7830a --- /dev/null +++ b/src/test/zones/evpn/ipv4/expected_controller_config @@ -0,0 +1,41 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/ipv4/expected_sdn_interfaces b/src/test/zones/evpn/ipv4/expected_sdn_interfaces new file mode 100644 index 0000000..9d1c64c --- /dev/null +++ b/src/test/zones/evpn/ipv4/expected_sdn_interfaces @@ -0,0 +1,42 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/ipv4/interfaces b/src/test/zones/evpn/ipv4/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/ipv4/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/ipv4/sdn_config b/src/test/zones/evpn/ipv4/sdn_config new file mode 100644 index 0000000..dd73b5c --- /dev/null +++ b/src/test/zones/evpn/ipv4/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/evpn/ipv4ipv6/expected_controller_config b/src/test/zones/evpn/ipv4ipv6/expected_controller_config new file mode 100644 index 0000000..bd7830a --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6/expected_controller_config @@ -0,0 +1,41 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces b/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces new file mode 100644 index 0000000..7a5d741 --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces @@ -0,0 +1,44 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + address 2a08:2142:302:3::1/64 + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + ip6-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/ipv4ipv6/interfaces b/src/test/zones/evpn/ipv4ipv6/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/ipv4ipv6/sdn_config b/src/test/zones/evpn/ipv4ipv6/sdn_config new file mode 100644 index 0000000..4583818 --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6/sdn_config @@ -0,0 +1,32 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { + 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + }, + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '2a08:2142:302:3::1', + } + } + } +} + + diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config b/src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config new file mode 100644 index 0000000..bd7830a --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config @@ -0,0 +1,41 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces b/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces new file mode 100644 index 0000000..378fa77 --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces @@ -0,0 +1,40 @@ +#version:1 + +auto myvnet +iface myvnet + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/interfaces b/src/test/zones/evpn/ipv4ipv6nogateway/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6nogateway/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/sdn_config b/src/test/zones/evpn/ipv4ipv6nogateway/sdn_config new file mode 100644 index 0000000..ab2273f --- /dev/null +++ b/src/test/zones/evpn/ipv4ipv6nogateway/sdn_config @@ -0,0 +1,30 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { + 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} + + diff --git a/src/test/zones/evpn/ipv6/expected_controller_config b/src/test/zones/evpn/ipv6/expected_controller_config new file mode 100644 index 0000000..bd7830a --- /dev/null +++ b/src/test/zones/evpn/ipv6/expected_controller_config @@ -0,0 +1,41 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/ipv6/expected_sdn_interfaces b/src/test/zones/evpn/ipv6/expected_sdn_interfaces new file mode 100644 index 0000000..b2bdbfe --- /dev/null +++ b/src/test/zones/evpn/ipv6/expected_sdn_interfaces @@ -0,0 +1,42 @@ +#version:1 + +auto myvnet +iface myvnet + address 2a08:2142:302:3::1/64 + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip6-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/ipv6/interfaces b/src/test/zones/evpn/ipv6/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/ipv6/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/ipv6/sdn_config b/src/test/zones/evpn/ipv6/sdn_config new file mode 100644 index 0000000..949e886 --- /dev/null +++ b/src/test/zones/evpn/ipv6/sdn_config @@ -0,0 +1,27 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '2a08:2142:302:3::1', + } + } + } +} + + diff --git a/src/test/zones/evpn/multipath_relax/expected_controller_config b/src/test/zones/evpn/multipath_relax/expected_controller_config new file mode 100644 index 0000000..2d1ad44 --- /dev/null +++ b/src/test/zones/evpn/multipath_relax/expected_controller_config @@ -0,0 +1,53 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + bgp bestpath as-path multipath-relax + neighbor BGP peer-group + neighbor BGP remote-as 65000 + neighbor BGP bfd + neighbor 192.168.0.1 peer-group BGP + neighbor 192.168.0.2 peer-group BGP + neighbor 192.168.0.3 peer-group BGP + ! + address-family ipv4 unicast + neighbor BGP activate + neighbor BGP soft-reconfiguration inbound + exit-address-family + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/multipath_relax/expected_sdn_interfaces b/src/test/zones/evpn/multipath_relax/expected_sdn_interfaces new file mode 100644 index 0000000..4cf13e0 --- /dev/null +++ b/src/test/zones/evpn/multipath_relax/expected_sdn_interfaces @@ -0,0 +1,41 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/multipath_relax/interfaces b/src/test/zones/evpn/multipath_relax/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/multipath_relax/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/multipath_relax/sdn_config b/src/test/zones/evpn/multipath_relax/sdn_config new file mode 100644 index 0000000..5a1d8a7 --- /dev/null +++ b/src/test/zones/evpn/multipath_relax/sdn_config @@ -0,0 +1,49 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { + tag => "100", + type => "vnet", + zone => "myzone", + }, + }, + }, + + zones => { + ids => { + myzone => { + ipam => "pve", + type => "evpn", + controller => "evpnctl", + 'vrf-vxlan' => 1000, + }, + }, + }, + controllers => { + ids => { + evpnctl => { + type => "evpn", + 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', + asn => "65000", + }, + localhost => { + type => "bgp", + 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', + 'bgp-multipath-as-path-relax' => "1", + asn => "65000", + node => "localhost", + }, + }, + }, + + subnets => { + ids => { + 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + }, + }, + }, +} diff --git a/src/test/zones/evpn/rt_import/expected_controller_config b/src/test/zones/evpn/rt_import/expected_controller_config new file mode 100644 index 0000000..f4f28dd --- /dev/null +++ b/src/test/zones/evpn/rt_import/expected_controller_config @@ -0,0 +1,47 @@ +frr version 8.2.2 +frr defaults datacenter +hostname localhost +log syslog informational +service integrated-vtysh-config +! +! +vrf vrf_myzone + vni 1000 +exit-vrf +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp default ipv4-unicast + coalesce-time 1000 + neighbor VTEP peer-group + neighbor VTEP remote-as 65000 + neighbor VTEP bfd + neighbor 192.168.0.2 peer-group VTEP + neighbor 192.168.0.3 peer-group VTEP + ! + address-family l2vpn evpn + neighbor VTEP route-map MAP_VTEP_IN in + neighbor VTEP route-map MAP_VTEP_OUT out + neighbor VTEP activate + advertise-all-vni + exit-address-family +exit +! +router bgp 65000 vrf vrf_myzone + bgp router-id 192.168.0.1 + ! + address-family l2vpn evpn + route-target import 65001:1000 + route-target import 65002:1000 + route-target import 65003:1000 + exit-address-family +exit +! +route-map MAP_VTEP_IN permit 1 +exit +! +route-map MAP_VTEP_OUT permit 1 +exit +! +line vty +! \ No newline at end of file diff --git a/src/test/zones/evpn/rt_import/expected_sdn_interfaces b/src/test/zones/evpn/rt_import/expected_sdn_interfaces new file mode 100644 index 0000000..9d1c64c --- /dev/null +++ b/src/test/zones/evpn/rt_import/expected_sdn_interfaces @@ -0,0 +1,42 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + hwaddress A2:1D:CB:1A:C0:8B + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + ip-forward on + arp-accept on + vrf vrf_myzone + +auto vrf_myzone +iface vrf_myzone + vrf-table auto + post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 + +auto vrfbr_myzone +iface vrfbr_myzone + bridge-ports vrfvx_myzone + bridge_stp off + bridge_fd 0 + mtu 1450 + vrf vrf_myzone + +auto vrfvx_myzone +iface vrfvx_myzone + vxlan-id 1000 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan-local-tunnelip 192.168.0.1 + bridge-learning off + bridge-arp-nd-suppress on + mtu 1450 diff --git a/src/test/zones/evpn/rt_import/interfaces b/src/test/zones/evpn/rt_import/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/evpn/rt_import/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/evpn/rt_import/sdn_config b/src/test/zones/evpn/rt_import/sdn_config new file mode 100644 index 0000000..b62bb2e --- /dev/null +++ b/src/test/zones/evpn/rt_import/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", zone => "myzone" }, + }, + }, + + zones => { + ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'rt-import' => '65001:1000,65002:1000,65003:1000' } }, + }, + controllers => { + ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + } + } + } +} + + diff --git a/src/test/zones/qinq/bridge/expected_sdn_interfaces b/src/test/zones/qinq/bridge/expected_sdn_interfaces new file mode 100644 index 0000000..58a0e23 --- /dev/null +++ b/src/test/zones/qinq/bridge/expected_sdn_interfaces @@ -0,0 +1,65 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto ln_myzone2 +iface ln_myzone2 + link-type veth + veth-peer-name pr_myzone2 + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto myvnet2 +iface myvnet2 + bridge_ports z_myzone.101 + bridge_stp off + bridge_fd 0 + +auto myvnet3 +iface myvnet3 + bridge_ports z_myzone2.100 + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto pr_myzone2 +iface pr_myzone2 + link-type veth + veth-peer-name ln_myzone2 + +auto sv_myzone +iface sv_myzone + vlan-raw-device eth0 + vlan-id 10 + +auto sv_myzone2 +iface sv_myzone2 + vlan-raw-device eth0 + vlan-id 20 + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto z_myzone2 +iface z_myzone2 + bridge-stp off + bridge-ports sv_myzone2 ln_myzone2 + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge/interfaces b/src/test/zones/qinq/bridge/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/qinq/bridge/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/qinq/bridge/sdn_config b/src/test/zones/qinq/bridge/sdn_config new file mode 100644 index 0000000..6321603 --- /dev/null +++ b/src/test/zones/qinq/bridge/sdn_config @@ -0,0 +1,16 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + myvnet2 => { tag => 101, type => "vnet", zone => "myzone" }, + myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" }, + }, + }, + zones => { + ids => { + myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" }, + myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" }, + }, + }, +} diff --git a/src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces new file mode 100644 index 0000000..cfa43a2 --- /dev/null +++ b/src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces @@ -0,0 +1,36 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto myvnet2 +iface myvnet2 + bridge_ports pr_myzone + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto sv_myzone +iface sv_myzone + vlan-raw-device eth0 + vlan-id 10 + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_notagvnet/interfaces b/src/test/zones/qinq/bridge_notagvnet/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/qinq/bridge_notagvnet/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/qinq/bridge_notagvnet/sdn_config b/src/test/zones/qinq/bridge_notagvnet/sdn_config new file mode 100644 index 0000000..1f40369 --- /dev/null +++ b/src/test/zones/qinq/bridge_notagvnet/sdn_config @@ -0,0 +1,26 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { + tag => 100, + type => "vnet", + zone => "myzone" + }, + myvnet2 => { + type => "vnet", + zone => "myzone" + }, + }, + }, + zones => { + ids => { + myzone => { + bridge => "vmbr0", + tag => 10, + ipam => "pve", + type => "qinq", + }, + }, + }, +} diff --git a/src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces new file mode 100644 index 0000000..c325dec --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces @@ -0,0 +1,55 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto ln_myzone2 +iface ln_myzone2 + link-type veth + veth-peer-name pr_myzone2 + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto myvnet2 +iface myvnet2 + bridge_ports z_myzone.101 + bridge_stp off + bridge_fd 0 + +auto myvnet3 +iface myvnet3 + bridge_ports z_myzone2.100 + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto pr_myzone2 +iface pr_myzone2 + link-type veth + veth-peer-name ln_myzone2 + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports vmbr0.10 ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto z_myzone2 +iface z_myzone2 + bridge-stp off + bridge-ports vmbr0.20 ln_myzone2 + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_vlanaware/interfaces b/src/test/zones/qinq/bridge_vlanaware/interfaces new file mode 100644 index 0000000..cfdfafe --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 + bridge-vids 2-4094 + bridge-vlan-aware 1 diff --git a/src/test/zones/qinq/bridge_vlanaware/sdn_config b/src/test/zones/qinq/bridge_vlanaware/sdn_config new file mode 100644 index 0000000..6321603 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware/sdn_config @@ -0,0 +1,16 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + myvnet2 => { tag => 101, type => "vnet", zone => "myzone" }, + myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" }, + }, + }, + zones => { + ids => { + myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" }, + myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" }, + }, + }, +} diff --git a/src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces new file mode 100644 index 0000000..cd87a3a --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces @@ -0,0 +1,27 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports pr_myzone + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports vmbr0.10 ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces b/src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces new file mode 100644 index 0000000..cfdfafe --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 + bridge-vids 2-4094 + bridge-vlan-aware 1 diff --git a/src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config b/src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config new file mode 100644 index 0000000..2382f4d --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces new file mode 100644 index 0000000..28d215b --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces @@ -0,0 +1,27 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports vmbr0.10 ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces new file mode 100644 index 0000000..cfdfafe --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 + bridge-vids 2-4094 + bridge-vlan-aware 1 diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config new file mode 100644 index 0000000..c013176 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces new file mode 100644 index 0000000..0bc301b --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces @@ -0,0 +1,29 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto vmbr0 +iface vmbr0 + bridge-vlan-protocol 802.1ad + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports vmbr0.10 ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces new file mode 100644 index 0000000..cfdfafe --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 + bridge-vids 2-4094 + bridge-vlan-aware 1 diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config new file mode 100644 index 0000000..20a8a51 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces new file mode 100644 index 0000000..bde23d9 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces @@ -0,0 +1,32 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto sv_myzone +iface sv_myzone + vlan-raw-device eth0 + vlan-id 10 + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_vlanawarevnet/interfaces b/src/test/zones/qinq/bridge_vlanawarevnet/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanawarevnet/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/qinq/bridge_vlanawarevnet/sdn_config b/src/test/zones/qinq/bridge_vlanawarevnet/sdn_config new file mode 100644 index 0000000..c013176 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanawarevnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces new file mode 100644 index 0000000..6b59164 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces @@ -0,0 +1,31 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto sv_myzone +iface sv_myzone + vlan-raw-device eth0 + vlan-id 10 + vlan-protocol 802.1ad + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/bridge_vlanprotocol/interfaces b/src/test/zones/qinq/bridge_vlanprotocol/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanprotocol/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/qinq/bridge_vlanprotocol/sdn_config b/src/test/zones/qinq/bridge_vlanprotocol/sdn_config new file mode 100644 index 0000000..20a8a51 --- /dev/null +++ b/src/test/zones/qinq/bridge_vlanprotocol/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/ovs/expected_sdn_interfaces b/src/test/zones/qinq/ovs/expected_sdn_interfaces new file mode 100644 index 0000000..d25b2a8 --- /dev/null +++ b/src/test/zones/qinq/ovs/expected_sdn_interfaces @@ -0,0 +1,71 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto ln_myzone2 +iface ln_myzone2 + link-type veth + veth-peer-name pr_myzone2 + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto myvnet2 +iface myvnet2 + bridge_ports z_myzone.101 + bridge_stp off + bridge_fd 0 + +auto myvnet3 +iface myvnet3 + bridge_ports z_myzone2.100 + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto pr_myzone2 +iface pr_myzone2 + link-type veth + veth-peer-name ln_myzone2 + +auto sv_myzone +iface sv_myzone + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q + +auto sv_myzone2 +iface sv_myzone2 + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options vlan_mode=dot1q-tunnel tag=20 other_config:qinq-ethtype=802.1q + +auto vmbr0 +iface vmbr0 + ovs_ports sv_myzone sv_myzone2 + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto z_myzone2 +iface z_myzone2 + bridge-stp off + bridge-ports sv_myzone2 ln_myzone2 + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/ovs/interfaces b/src/test/zones/qinq/ovs/interfaces new file mode 100644 index 0000000..14d2f1e --- /dev/null +++ b/src/test/zones/qinq/ovs/interfaces @@ -0,0 +1,9 @@ +auto eth0 +iface eth0 inet manual + ovs_type OVSPort + ovs_bridge vmbr0 + +auto vmbr0 +iface vmbr0 inet manual + ovs_type OVSBridge + ovs_ports eth0 diff --git a/src/test/zones/qinq/ovs/sdn_config b/src/test/zones/qinq/ovs/sdn_config new file mode 100644 index 0000000..6321603 --- /dev/null +++ b/src/test/zones/qinq/ovs/sdn_config @@ -0,0 +1,16 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + myvnet2 => { tag => 101, type => "vnet", zone => "myzone" }, + myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" }, + }, + }, + zones => { + ids => { + myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" }, + myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" }, + }, + }, +} diff --git a/src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces b/src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces new file mode 100644 index 0000000..5f47b28 --- /dev/null +++ b/src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces @@ -0,0 +1,37 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports pr_myzone + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto sv_myzone +iface sv_myzone + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q + +auto vmbr0 +iface vmbr0 + ovs_ports sv_myzone + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/ovs_notagvnet/interfaces b/src/test/zones/qinq/ovs_notagvnet/interfaces new file mode 100644 index 0000000..14d2f1e --- /dev/null +++ b/src/test/zones/qinq/ovs_notagvnet/interfaces @@ -0,0 +1,9 @@ +auto eth0 +iface eth0 inet manual + ovs_type OVSPort + ovs_bridge vmbr0 + +auto vmbr0 +iface vmbr0 inet manual + ovs_type OVSBridge + ovs_ports eth0 diff --git a/src/test/zones/qinq/ovs_notagvnet/sdn_config b/src/test/zones/qinq/ovs_notagvnet/sdn_config new file mode 100644 index 0000000..2382f4d --- /dev/null +++ b/src/test/zones/qinq/ovs_notagvnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces new file mode 100644 index 0000000..d69d38c --- /dev/null +++ b/src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces @@ -0,0 +1,37 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto sv_myzone +iface sv_myzone + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q + +auto vmbr0 +iface vmbr0 + ovs_ports sv_myzone + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/ovs_vlanawarevnet/interfaces b/src/test/zones/qinq/ovs_vlanawarevnet/interfaces new file mode 100644 index 0000000..14d2f1e --- /dev/null +++ b/src/test/zones/qinq/ovs_vlanawarevnet/interfaces @@ -0,0 +1,9 @@ +auto eth0 +iface eth0 inet manual + ovs_type OVSPort + ovs_bridge vmbr0 + +auto vmbr0 +iface vmbr0 inet manual + ovs_type OVSBridge + ovs_ports eth0 diff --git a/src/test/zones/qinq/ovs_vlanawarevnet/sdn_config b/src/test/zones/qinq/ovs_vlanawarevnet/sdn_config new file mode 100644 index 0000000..c013176 --- /dev/null +++ b/src/test/zones/qinq/ovs_vlanawarevnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces b/src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces new file mode 100644 index 0000000..aeefec9 --- /dev/null +++ b/src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces @@ -0,0 +1,35 @@ +#version:1 + +auto ln_myzone +iface ln_myzone + link-type veth + veth-peer-name pr_myzone + +auto myvnet +iface myvnet + bridge_ports z_myzone.100 + bridge_stp off + bridge_fd 0 + +auto pr_myzone +iface pr_myzone + link-type veth + veth-peer-name ln_myzone + +auto sv_myzone +iface sv_myzone + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1ad + +auto vmbr0 +iface vmbr0 + ovs_ports sv_myzone + +auto z_myzone +iface z_myzone + bridge-stp off + bridge-ports sv_myzone ln_myzone + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/qinq/ovs_vlanprotocol/interfaces b/src/test/zones/qinq/ovs_vlanprotocol/interfaces new file mode 100644 index 0000000..14d2f1e --- /dev/null +++ b/src/test/zones/qinq/ovs_vlanprotocol/interfaces @@ -0,0 +1,9 @@ +auto eth0 +iface eth0 inet manual + ovs_type OVSPort + ovs_bridge vmbr0 + +auto vmbr0 +iface vmbr0 inet manual + ovs_type OVSBridge + ovs_ports eth0 diff --git a/src/test/zones/qinq/ovs_vlanprotocol/sdn_config b/src/test/zones/qinq/ovs_vlanprotocol/sdn_config new file mode 100644 index 0000000..20a8a51 --- /dev/null +++ b/src/test/zones/qinq/ovs_vlanprotocol/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } }, + }, +} diff --git a/src/test/zones/simple/basic/expected_sdn_interfaces b/src/test/zones/simple/basic/expected_sdn_interfaces new file mode 100644 index 0000000..1e0c2c7 --- /dev/null +++ b/src/test/zones/simple/basic/expected_sdn_interfaces @@ -0,0 +1,7 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports none + bridge_stp off + bridge_fd 0 diff --git a/src/test/zones/simple/basic/interfaces b/src/test/zones/simple/basic/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/simple/basic/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/simple/basic/sdn_config b/src/test/zones/simple/basic/sdn_config new file mode 100644 index 0000000..527dcba --- /dev/null +++ b/src/test/zones/simple/basic/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, +} diff --git a/src/test/zones/simple/hetzner/expected_sdn_interfaces b/src/test/zones/simple/hetzner/expected_sdn_interfaces new file mode 100644 index 0000000..f47ac53 --- /dev/null +++ b/src/test/zones/simple/hetzner/expected_sdn_interfaces @@ -0,0 +1,19 @@ +#version:1 + +auto myvnet +iface myvnet + address 144.76.100.65/29 + bridge_ports none + bridge_stp off + bridge_fd 0 + ip-forward on + +auto myvnet2 +iface myvnet2 + address 144.76.0.1/32 + up ip route add 144.76.200.65/32 dev myvnet2 + up ip route add 144.76.200.66/32 dev myvnet2 + bridge_ports none + bridge_stp off + bridge_fd 0 + ip-forward on diff --git a/src/test/zones/simple/hetzner/interfaces b/src/test/zones/simple/hetzner/interfaces new file mode 100644 index 0000000..5ab9635 --- /dev/null +++ b/src/test/zones/simple/hetzner/interfaces @@ -0,0 +1,6 @@ +auto eth0 +iface eth0 inet static + address 144.76.0.1 + netmask 255.255.255.255 + pointopoint 172.31.1.1 + gateway 172.31.1.1 \ No newline at end of file diff --git a/src/test/zones/simple/hetzner/sdn_config b/src/test/zones/simple/hetzner/sdn_config new file mode 100644 index 0000000..30773ca --- /dev/null +++ b/src/test/zones/simple/hetzner/sdn_config @@ -0,0 +1,34 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + myvnet2 => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, + + subnets => { + ids => { + 'myzone-144.76.100.64-29' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '144.76.100.65', + }, + 'myzone-144.76.200.65-32' => { + 'type' => 'subnet', + 'vnet' => 'myvnet2', + 'gateway' => '144.76.0.1', + }, + 'myzone-144.76.200.66-32' => { + 'type' => 'subnet', + 'vnet' => 'myvnet2', + 'gateway' => '144.76.0.1', + }, + } + } +} + + diff --git a/src/test/zones/simple/ipv4/expected_sdn_interfaces b/src/test/zones/simple/ipv4/expected_sdn_interfaces new file mode 100644 index 0000000..06e43ad --- /dev/null +++ b/src/test/zones/simple/ipv4/expected_sdn_interfaces @@ -0,0 +1,9 @@ +#version:1 + +auto myvnet +iface myvnet + address 192.168.0.1/24 + bridge_ports none + bridge_stp off + bridge_fd 0 + ip-forward on diff --git a/src/test/zones/simple/ipv4/interfaces b/src/test/zones/simple/ipv4/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/simple/ipv4/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/simple/ipv4/sdn_config b/src/test/zones/simple/ipv4/sdn_config new file mode 100644 index 0000000..dd77b75 --- /dev/null +++ b/src/test/zones/simple/ipv4/sdn_config @@ -0,0 +1,22 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, + + subnets => { + ids => { 'myzone-192.168.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '192.168.0.1', + } + } + } +} + + diff --git a/src/test/zones/simple/ipv4snat/expected_sdn_interfaces b/src/test/zones/simple/ipv4snat/expected_sdn_interfaces new file mode 100644 index 0000000..69d7986 --- /dev/null +++ b/src/test/zones/simple/ipv4snat/expected_sdn_interfaces @@ -0,0 +1,13 @@ +#version:1 + +auto myvnet +iface myvnet + address 10.0.0.1/24 + post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 + bridge_ports none + bridge_stp off + bridge_fd 0 + ip-forward on diff --git a/src/test/zones/simple/ipv4snat/interfaces b/src/test/zones/simple/ipv4snat/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/simple/ipv4snat/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/simple/ipv4snat/sdn_config b/src/test/zones/simple/ipv4snat/sdn_config new file mode 100644 index 0000000..5936d7d --- /dev/null +++ b/src/test/zones/simple/ipv4snat/sdn_config @@ -0,0 +1,23 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, + + subnets => { + ids => { 'myzone-10.0.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '10.0.0.1', + 'snat' => 1 + } + } + } +} + + diff --git a/src/test/zones/simple/ipv4v6/expected_sdn_interfaces b/src/test/zones/simple/ipv4v6/expected_sdn_interfaces new file mode 100644 index 0000000..34ed5db --- /dev/null +++ b/src/test/zones/simple/ipv4v6/expected_sdn_interfaces @@ -0,0 +1,11 @@ +#version:1 + +auto myvnet +iface myvnet + address 192.168.0.1/24 + address 2a08:2142:302:3::1/64 + bridge_ports none + bridge_stp off + bridge_fd 0 + ip-forward on + ip6-forward on diff --git a/src/test/zones/simple/ipv4v6/interfaces b/src/test/zones/simple/ipv4v6/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/simple/ipv4v6/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/simple/ipv4v6/sdn_config b/src/test/zones/simple/ipv4v6/sdn_config new file mode 100644 index 0000000..b8ed848 --- /dev/null +++ b/src/test/zones/simple/ipv4v6/sdn_config @@ -0,0 +1,27 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, + subnets => { + ids => { + 'myzone-192.168.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '192.168.0.1', + }, + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '2a08:2142:302:3::1', + } + } + } +} + + diff --git a/src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces b/src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces new file mode 100644 index 0000000..1e0c2c7 --- /dev/null +++ b/src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces @@ -0,0 +1,7 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports none + bridge_stp off + bridge_fd 0 diff --git a/src/test/zones/simple/ipv4v6nogateway/interfaces b/src/test/zones/simple/ipv4v6nogateway/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/simple/ipv4v6nogateway/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/simple/ipv4v6nogateway/sdn_config b/src/test/zones/simple/ipv4v6nogateway/sdn_config new file mode 100644 index 0000000..dbd75c9 --- /dev/null +++ b/src/test/zones/simple/ipv4v6nogateway/sdn_config @@ -0,0 +1,25 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, + subnets => { + ids => { + 'myzone-192.168.0.0-24' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + }, + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + } + } + } +} + + diff --git a/src/test/zones/simple/ipv6snat/expected_sdn_interfaces b/src/test/zones/simple/ipv6snat/expected_sdn_interfaces new file mode 100644 index 0000000..d3adc24 --- /dev/null +++ b/src/test/zones/simple/ipv6snat/expected_sdn_interfaces @@ -0,0 +1,13 @@ +#version:1 + +auto myvnet +iface myvnet + address 2a08:2142:302:3::1/64 + post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 + post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 + post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 + bridge_ports none + bridge_stp off + bridge_fd 0 + ip6-forward on diff --git a/src/test/zones/simple/ipv6snat/interfaces b/src/test/zones/simple/ipv6snat/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/simple/ipv6snat/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/simple/ipv6snat/sdn_config b/src/test/zones/simple/ipv6snat/sdn_config new file mode 100644 index 0000000..bc38527 --- /dev/null +++ b/src/test/zones/simple/ipv6snat/sdn_config @@ -0,0 +1,24 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "simple" } }, + }, + + subnets => { + ids => { + 'myzone-2a08:2142:302:3::-64' => { + 'type' => 'subnet', + 'vnet' => 'myvnet', + 'gateway' => '2a08:2142:302:3::1', + 'snat' => 1 + } + } + } +} + + diff --git a/src/test/zones/vlan/bridge/expected_sdn_interfaces b/src/test/zones/vlan/bridge/expected_sdn_interfaces new file mode 100644 index 0000000..f9e96d1 --- /dev/null +++ b/src/test/zones/vlan/bridge/expected_sdn_interfaces @@ -0,0 +1,23 @@ +#version:1 + +auto ln_myvnet +iface ln_myvnet + link-type veth + veth-peer-name pr_myvnet + +auto myvnet +iface myvnet + bridge_ports ln_myvnet + bridge_stp off + bridge_fd 0 + +auto pr_myvnet +iface pr_myvnet + link-type veth + veth-peer-name ln_myvnet + +auto vmbr0v100 +iface vmbr0v100 + bridge_ports eth0.100 pr_myvnet + bridge_stp off + bridge_fd 0 diff --git a/src/test/zones/vlan/bridge/interfaces b/src/test/zones/vlan/bridge/interfaces new file mode 100644 index 0000000..68b6a88 --- /dev/null +++ b/src/test/zones/vlan/bridge/interfaces @@ -0,0 +1,5 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/vlan/bridge/sdn_config b/src/test/zones/vlan/bridge/sdn_config new file mode 100644 index 0000000..c6cfaaa --- /dev/null +++ b/src/test/zones/vlan/bridge/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, + }, +} diff --git a/src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces b/src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces new file mode 100644 index 0000000..a318c7a --- /dev/null +++ b/src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces @@ -0,0 +1,7 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports vmbr0.100 + bridge_stp off + bridge_fd 0 diff --git a/src/test/zones/vlan/bridge_vlanaware/interfaces b/src/test/zones/vlan/bridge_vlanaware/interfaces new file mode 100644 index 0000000..cfdfafe --- /dev/null +++ b/src/test/zones/vlan/bridge_vlanaware/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 + bridge-vids 2-4094 + bridge-vlan-aware 1 diff --git a/src/test/zones/vlan/bridge_vlanaware/sdn_config b/src/test/zones/vlan/bridge_vlanaware/sdn_config new file mode 100644 index 0000000..c6cfaaa --- /dev/null +++ b/src/test/zones/vlan/bridge_vlanaware/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, + }, +} diff --git a/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces new file mode 100644 index 0000000..ebf9d2e --- /dev/null +++ b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces @@ -0,0 +1,9 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports vmbr0.100 + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 diff --git a/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces new file mode 100644 index 0000000..64eec9e --- /dev/null +++ b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet manual + bridge-ports eth0 + bridge-stp off + bridge-fd 0 + bridge-vlan-aware yes + bridge-vids 2-4096 diff --git a/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config new file mode 100644 index 0000000..67068f9 --- /dev/null +++ b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => "100", type => "vnet", vlanaware => 1, zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, + }, +} diff --git a/src/test/zones/vlan/ovs/expected_sdn_interfaces b/src/test/zones/vlan/ovs/expected_sdn_interfaces new file mode 100644 index 0000000..044559e --- /dev/null +++ b/src/test/zones/vlan/ovs/expected_sdn_interfaces @@ -0,0 +1,17 @@ +#version:1 + +auto ln_myvnet +iface ln_myvnet + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options tag=100 + +auto myvnet +iface myvnet + bridge_ports ln_myvnet + bridge_stp off + bridge_fd 0 + +auto vmbr0 +iface vmbr0 + ovs_ports ln_myvnet diff --git a/src/test/zones/vlan/ovs/interfaces b/src/test/zones/vlan/ovs/interfaces new file mode 100644 index 0000000..14d2f1e --- /dev/null +++ b/src/test/zones/vlan/ovs/interfaces @@ -0,0 +1,9 @@ +auto eth0 +iface eth0 inet manual + ovs_type OVSPort + ovs_bridge vmbr0 + +auto vmbr0 +iface vmbr0 inet manual + ovs_type OVSBridge + ovs_ports eth0 diff --git a/src/test/zones/vlan/ovs/sdn_config b/src/test/zones/vlan/ovs/sdn_config new file mode 100644 index 0000000..c6cfaaa --- /dev/null +++ b/src/test/zones/vlan/ovs/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, + }, +} diff --git a/src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces b/src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces new file mode 100644 index 0000000..7bb73b6 --- /dev/null +++ b/src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces @@ -0,0 +1,19 @@ +#version:1 + +auto ln_myvnet +iface ln_myvnet + ovs_type OVSIntPort + ovs_bridge vmbr0 + ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=100 + +auto myvnet +iface myvnet + bridge_ports ln_myvnet + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + +auto vmbr0 +iface vmbr0 + ovs_ports ln_myvnet diff --git a/src/test/zones/vlan/ovs_vlanware_vnet/interfaces b/src/test/zones/vlan/ovs_vlanware_vnet/interfaces new file mode 100644 index 0000000..14d2f1e --- /dev/null +++ b/src/test/zones/vlan/ovs_vlanware_vnet/interfaces @@ -0,0 +1,9 @@ +auto eth0 +iface eth0 inet manual + ovs_type OVSPort + ovs_bridge vmbr0 + +auto vmbr0 +iface vmbr0 inet manual + ovs_type OVSBridge + ovs_ports eth0 diff --git a/src/test/zones/vlan/ovs_vlanware_vnet/sdn_config b/src/test/zones/vlan/ovs_vlanware_vnet/sdn_config new file mode 100644 index 0000000..9cfdb52 --- /dev/null +++ b/src/test/zones/vlan/ovs_vlanware_vnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, + }, +} diff --git a/src/test/zones/vxlan/basic/expected_sdn_interfaces b/src/test/zones/vxlan/basic/expected_sdn_interfaces new file mode 100644 index 0000000..7b73c3e --- /dev/null +++ b/src/test/zones/vxlan/basic/expected_sdn_interfaces @@ -0,0 +1,15 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan_remoteip 192.168.0.2 + vxlan_remoteip 192.168.0.3 + mtu 1450 diff --git a/src/test/zones/vxlan/basic/interfaces b/src/test/zones/vxlan/basic/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/vxlan/basic/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/vxlan/basic/sdn_config b/src/test/zones/vxlan/basic/sdn_config new file mode 100644 index 0000000..f929304 --- /dev/null +++ b/src/test/zones/vxlan/basic/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } }, + }, +} diff --git a/src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces b/src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces new file mode 100644 index 0000000..55cdf9c --- /dev/null +++ b/src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces @@ -0,0 +1,17 @@ +#version:1 + +auto myvnet +iface myvnet + bridge_ports vxlan_myvnet + bridge_stp off + bridge_fd 0 + bridge-vlan-aware yes + bridge-vids 2-4094 + mtu 1450 + +auto vxlan_myvnet +iface vxlan_myvnet + vxlan-id 100 + vxlan_remoteip 192.168.0.2 + vxlan_remoteip 192.168.0.3 + mtu 1450 diff --git a/src/test/zones/vxlan/vlanawarevnet/interfaces b/src/test/zones/vxlan/vlanawarevnet/interfaces new file mode 100644 index 0000000..66bb826 --- /dev/null +++ b/src/test/zones/vxlan/vlanawarevnet/interfaces @@ -0,0 +1,7 @@ +auto vmbr0 +iface vmbr0 inet static + address 192.168.0.1/24 + gateway 192.168.0.254 + bridge-ports eth0 + bridge-stp off + bridge-fd 0 diff --git a/src/test/zones/vxlan/vlanawarevnet/sdn_config b/src/test/zones/vxlan/vlanawarevnet/sdn_config new file mode 100644 index 0000000..23fb557 --- /dev/null +++ b/src/test/zones/vxlan/vlanawarevnet/sdn_config @@ -0,0 +1,11 @@ +{ + version => 1, + vnets => { + ids => { + myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, + }, + }, + zones => { + ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } }, + }, +} diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index eedc4e0..0000000 --- a/test/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -all: test - -test: test_zones test_ipams test_dns test_subnets - -test_zones: run_test_zones.pl - ./run_test_zones.pl - -test_ipams: run_test_ipams.pl - ./run_test_ipams.pl - -test_dns: run_test_dns.pl - ./run_test_dns.pl - -test_subnets: run_test_subnets.pl - ./run_test_subnets.pl diff --git a/test/debug/documentation.txt b/test/debug/documentation.txt deleted file mode 100644 index 6ee8ee6..0000000 --- a/test/debug/documentation.txt +++ /dev/null @@ -1,102 +0,0 @@ -Here a sample of command with pvesh to manage the sdn. - - -#create a vlan transportzone -pvesh create /cluster/sdn/zones/ --zone vlanzone --type vlan --ipam pve --bridge vmbr0 -#create a vnet on vlanzone -pvesh create /cluster/sdn/vnets/ --vnet vnet100 --type vnet --zone vlanzone --tag 100 -#create a subnet on vlanzone -pvesh create /cluster/sdn/vnets/vnet100/subnets/ --type subnet --subnet 192.168.0.0/24 --gateway 192.168.0.1 - - -#create a layer2 vxlan unicast transportzone -pvesh create /cluster/sdn/zones/ --zone vxlanunicastzone --type vxlan --ipam pve --peers 192.168.0.1,192.168.0.2,192.168.0.3 - -#create an evpn controller -pvesh create /cluster/sdn/controllers/ --controller evpn1 --type evpn --peers 192.168.0.1,192.168.0.2,192.168.0.3 --asn 1234 - -#add a ebgp peer -pvesh create /cluster/sdn/controllers/ --controller bgp1 --type bgp --peers 192.168.0.253,192.168.0.254 --asn 1234 --ebgp --node pxnode1 - -#create a layer2 vxlan bgpevpn transportzone -pvesh create /cluster/sdn/zones/ --zone layer2evpnzone --type evpn --ipam pve --controller evpn1 - -#create a layer3 routable vxlan bgpevpn transportzone + exit-nodes -pvesh create /cluster/sdn/zones/ --zone layer3evpnzone --type evpn --ipam pve --controller evpn1 --vrf-vxlan 4000 --exit-nodes pxnode1,pxnode2 - - - -#create a vnet in the transportzone -pvesh create /cluster/sdn/vnets/ --vnet vnet10 --type vnet --zone vlanzone --tag 10 - -#create a vnet in the transportzone with subnets for evpn routing -pvesh create /cluster/sdn/vnets/ --vnet vnet11 --type vnet --zone layer3evpnzone --tag 11 --mac c8:1f:66:f8:62:8d -pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.0.0/24 --gateway 10.0.0.1 -pvesh create /cluster/sdn/vnets/ --vnet vnet12 --type vnet --zone layer3evpnzone --tag 12 --mac c8:1f:66:f8:62:8e -pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.1.0/24 --gateway 10.0.1.1 - -#display running configuration -pvesh get /cluster/sdn/vnets --running -pvesh get /cluster/sdn/zones --running -pvesh get /cluster/sdn/controllers --running -pvesh get /cluster/sdn/vnets/vnetX/subnets --running - - -#display pending configuration -pvesh get /cluster/sdn/vnets --pending -pvesh get /cluster/sdn/zones --pending -pvesh get /cluster/sdn/controllers --pending -pvesh get /cluster/sdn/vnets/vnetX/subnets --pending - - -#apply changes from /etc/pve/sdn.cfg.new to /etc/pve/sdn.cfg -pvesh set /cluster/sdn - - -#generate local /etc/network/interfaces.d/sdn and reload (need to be called on each node) - pvesh set /nodes//network - - -display transporzone status on all cluster nodes -#pvesh get /cluster/resources -┌────────────────────────────────────┬─────────┬───────┬───────────┬─────────┬───────┬────────┬─────────────┬────────────┬────────────┬───────────────┬──────┬───────────┬──────────────┬────────────────┐ -│ id │ type │ cpu │ disk │ hastate │ level │ maxcpu │ maxdisk │ maxmem │ mem │ node │ pool │ status │ storage │ uptime │ -│ sdn/node1/transportzone10 │ sdn │ │ │ │ │ │ │ │ │ kvmformation1 │ │ error │ │ │ -├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤ -│ sdn/node1/zone1 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │ -├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤ -│ sdn/node1/zone4 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │ -├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤ - - - - -#list all transport zones of a node - -pvesh get /nodes//sdn/zones/ - ┌─────────────────┬───────────┐ - │ sdn │ status │ - ├─────────────────┼───────────┤ - │ transportzone10 │ error │ - ├─────────────────┼───────────┤ - │ zone1 │ available │ - ├─────────────────┼───────────┤ - │ zone4 │ available │ - └─────────────────┴───────────┘ - - -#list all vnet status from a node transportzone - -pveset get /nodes//sdn/zones//content - - ┌─────────┬────────┐ - │ vnet │ status │ - ├─────────┼────────┤ - │ vnet100 │ error │ - ├─────────┼────────┤ - │ vnet101 │ error │ - └─────────┴────────┘ - - - - diff --git a/test/debug/generateconfig.pl b/test/debug/generateconfig.pl deleted file mode 100644 index 250db43..0000000 --- a/test/debug/generateconfig.pl +++ /dev/null @@ -1,24 +0,0 @@ -use strict; -use warnings; -use File::Copy; -use PVE::Cluster qw(cfs_read_file); - -use PVE::Network::SDN; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use Data::Dumper; - -PVE::Network::SDN::commit_config(); -my $network_config = PVE::Network::SDN::Zones::generate_etc_network_config(); - -PVE::Network::SDN::Zones::write_etc_network_config($network_config); -print "/etc/network/interfaces.d/sdn\n"; -print $network_config; -print "\n"; - -my $controller_config = PVE::Network::SDN::Controllers::generate_controller_config(); - -if ($controller_config) { - print Dumper($controller_config); - PVE::Network::SDN::Controllers::write_controller_config($controller_config); -} diff --git a/test/debug/statuscheck.pl b/test/debug/statuscheck.pl deleted file mode 100644 index e43003b..0000000 --- a/test/debug/statuscheck.pl +++ /dev/null @@ -1,9 +0,0 @@ -use strict; -use warnings; -use PVE::Network::SDN; -use Data::Dumper; - -my ($transport_status, $vnet_status) = PVE::Network::SDN::status(); - -print Dumper($vnet_status); -print Dumper($transport_status); diff --git a/test/dns/powerdns/dns_config b/test/dns/powerdns/dns_config deleted file mode 100644 index 6052366..0000000 --- a/test/dns/powerdns/dns_config +++ /dev/null @@ -1,10 +0,0 @@ -{ - 'ids' => { - 'powerdns' => { - 'url' => 'http://localhost:8881/api/v1/servers/localhost', - 'type' => 'powerdns', - 'key' => '1234', - 'ttl' => '3600' - }, - }, -} diff --git a/test/dns/powerdns/expected.add_a_multiple_record.ipv4 b/test/dns/powerdns/expected.add_a_multiple_record.ipv4 deleted file mode 100644 index 0e5539f..0000000 --- a/test/dns/powerdns/expected.add_a_multiple_record.ipv4 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"},{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.add_a_multiple_record.ipv6 b/test/dns/powerdns/expected.add_a_multiple_record.ipv6 deleted file mode 100644 index e432e7b..0000000 --- a/test/dns/powerdns/expected.add_a_multiple_record.ipv6 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"},{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.add_a_record.ipv4 b/test/dns/powerdns/expected.add_a_record.ipv4 deleted file mode 100644 index 888d67f..0000000 --- a/test/dns/powerdns/expected.add_a_record.ipv4 +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); \ No newline at end of file diff --git a/test/dns/powerdns/expected.add_a_record.ipv6 b/test/dns/powerdns/expected.add_a_record.ipv6 deleted file mode 100644 index bfeeab7..0000000 --- a/test/dns/powerdns/expected.add_a_record.ipv6 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.add_ptr_record.ipv4 b/test/dns/powerdns/expected.add_ptr_record.ipv4 deleted file mode 100644 index 6923971..0000000 --- a/test/dns/powerdns/expected.add_ptr_record.ipv4 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"1.0.0.10.in-addr.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"1.0.0.10.in-addr.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.add_ptr_record.ipv6 b/test/dns/powerdns/expected.add_ptr_record.ipv6 deleted file mode 100644 index 1d8049f..0000000 --- a/test/dns/powerdns/expected.add_ptr_record.ipv6 +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/dns/powerdns/expected.del_a_multiple_record.ipv4 b/test/dns/powerdns/expected.del_a_multiple_record.ipv4 deleted file mode 100644 index 45d76c6..0000000 --- a/test/dns/powerdns/expected.del_a_multiple_record.ipv4 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.del_a_multiple_record.ipv6 b/test/dns/powerdns/expected.del_a_multiple_record.ipv6 deleted file mode 100644 index 9b56abd..0000000 --- a/test/dns/powerdns/expected.del_a_multiple_record.ipv6 +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/dns/powerdns/expected.del_a_record.ipv4 b/test/dns/powerdns/expected.del_a_record.ipv4 deleted file mode 100644 index 7c0cf45..0000000 --- a/test/dns/powerdns/expected.del_a_record.ipv4 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"A"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.del_a_record.ipv6 b/test/dns/powerdns/expected.del_a_record.ipv6 deleted file mode 100644 index 9494c83..0000000 --- a/test/dns/powerdns/expected.del_a_record.ipv6 +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"AAAA"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/dns/powerdns/expected.del_ptr_record.ipv4 b/test/dns/powerdns/expected.del_ptr_record.ipv4 deleted file mode 100644 index 120485b..0000000 --- a/test/dns/powerdns/expected.del_ptr_record.ipv4 +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"DELETE","name":"1.0.0.10.in-addr.arpa.","records":[],"type":"PTR"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/dns/powerdns/expected.del_ptr_record.ipv6 b/test/dns/powerdns/expected.del_ptr_record.ipv6 deleted file mode 100644 index 7948e78..0000000 --- a/test/dns/powerdns/expected.del_ptr_record.ipv6 +++ /dev/null @@ -1,13 +0,0 @@ -bless( { - '_content' => '{"rrsets":[{"changetype":"DELETE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[],"type":"PTR"}]}', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' ) - }, 'HTTP::Request' ); - diff --git a/test/dns/powerdns/expected.verify_zone b/test/dns/powerdns/expected.verify_zone deleted file mode 100644 index b476875..0000000 --- a/test/dns/powerdns/expected.verify_zone +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '', - '_headers' => bless( { - '::std_case' => { - 'x-api-key' => 'X-API-Key' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'x-api-key' => '1234' - }, 'HTTP::Headers' ), - '_method' => 'GET', - '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com?rrsets=false')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/dns/powerdns/sdn_config b/test/dns/powerdns/sdn_config deleted file mode 100644 index 2087729..0000000 --- a/test/dns/powerdns/sdn_config +++ /dev/null @@ -1,20 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type =>"simple", dns => "powerdns", reversedns => "powerdns", dnszone => "domain.com" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} diff --git a/test/ipams/netbox/expected.add_ip b/test/ipams/netbox/expected.add_ip deleted file mode 100644 index ae876f2..0000000 --- a/test/ipams/netbox/expected.add_ip +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/expected.add_ip_notgateway b/test/ipams/netbox/expected.add_ip_notgateway deleted file mode 100644 index ae876f2..0000000 --- a/test/ipams/netbox/expected.add_ip_notgateway +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/expected.add_next_freeip b/test/ipams/netbox/expected.add_next_freeip deleted file mode 100644 index 7f80f4c..0000000 --- a/test/ipams/netbox/expected.add_next_freeip +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '{"description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/1/available-ips/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/expected.add_subnet b/test/ipams/netbox/expected.add_subnet deleted file mode 100644 index 62ca823..0000000 --- a/test/ipams/netbox/expected.add_subnet +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '{"prefix":"10.0.0.0/24"}', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/expected.del_ip b/test/ipams/netbox/expected.del_ip deleted file mode 100644 index 3c41de4..0000000 --- a/test/ipams/netbox/expected.del_ip +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'DELETE', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/expected.del_subnet b/test/ipams/netbox/expected.del_subnet deleted file mode 100644 index bdadb71..0000000 --- a/test/ipams/netbox/expected.del_subnet +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '{"address":"192.168.0.1/24","description":null,"dns_name":"toto"}', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/expected.update_ip b/test/ipams/netbox/expected.update_ip deleted file mode 100644 index a1202ad..0000000 --- a/test/ipams/netbox/expected.update_ip +++ /dev/null @@ -1,9 +0,0 @@ -bless( { - '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}', - '_headers' => bless( { - 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567', - 'content-type' => 'application/json; charset=UTF-8' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/netbox/ipam_config b/test/ipams/netbox/ipam_config deleted file mode 100644 index a33be30..0000000 --- a/test/ipams/netbox/ipam_config +++ /dev/null @@ -1,18 +0,0 @@ -{ - 'ids' => { - 'phpipam' => { - 'url' => 'https://localhost/api/apiadmin', - 'type' => 'phpipam', - 'section' => 1, - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, - 'pve' => { - 'type' => 'pve' - }, - 'netbox' => { - 'token' => '0123456789abcdef0123456789abcdef01234567', - 'type' => 'netbox', - 'url' => 'http://localhost:8000/api' - } - }, -} diff --git a/test/ipams/netbox/sdn_config b/test/ipams/netbox/sdn_config deleted file mode 100644 index c31847b..0000000 --- a/test/ipams/netbox/sdn_config +++ /dev/null @@ -1,20 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "netbox" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} diff --git a/test/ipams/phpipam/expected.add_ip b/test/ipams/phpipam/expected.add_ip deleted file mode 100644 index 50af460..0000000 --- a/test/ipams/phpipam/expected.add_ip +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","is_gateway":1,"mac":"da:65:8f:18:9b:6f","subnetId":1}', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/expected.add_ip_notgateway b/test/ipams/phpipam/expected.add_ip_notgateway deleted file mode 100644 index 7a91359..0000000 --- a/test/ipams/phpipam/expected.add_ip_notgateway +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","mac":"da:65:8f:18:9b:6f","subnetId":1}', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/expected.add_next_freeip b/test/ipams/phpipam/expected.add_next_freeip deleted file mode 100644 index d72f94f..0000000 --- a/test/ipams/phpipam/expected.add_next_freeip +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"description":"mydescription","hostname":"myhostname","mac":"da:65:8f:18:9b:6f"}', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/first_free/1/')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/expected.add_subnet b/test/ipams/phpipam/expected.add_subnet deleted file mode 100644 index b10cc5a..0000000 --- a/test/ipams/phpipam/expected.add_subnet +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"mask":"24","sectionId":1,"subnet":"10.0.0.0"}', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/subnets/')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/expected.del_ip b/test/ipams/phpipam/expected.del_ip deleted file mode 100644 index 72e83cb..0000000 --- a/test/ipams/phpipam/expected.del_ip +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'DELETE', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/expected.del_subnet b/test/ipams/phpipam/expected.del_subnet deleted file mode 100644 index 349a34f..0000000 --- a/test/ipams/phpipam/expected.del_subnet +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"description":null,"hostname":"toto","ip":"192.168.0.1","is_gateway":null,"subnetId":1}', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'POST', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/expected.update_ip b/test/ipams/phpipam/expected.update_ip deleted file mode 100644 index 96c219b..0000000 --- a/test/ipams/phpipam/expected.update_ip +++ /dev/null @@ -1,12 +0,0 @@ -bless( { - '_content' => '{"description":"mydescription","hostname":"myhostname","is_gateway":1,"mac":"da:65:8f:18:9b:6f"}', - '_headers' => bless( { - '::std_case' => { - 'token' => 'Token' - }, - 'content-type' => 'application/json; charset=UTF-8', - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, 'HTTP::Headers' ), - '_method' => 'PATCH', - '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' ) - }, 'HTTP::Request' ); diff --git a/test/ipams/phpipam/ipam_config b/test/ipams/phpipam/ipam_config deleted file mode 100644 index a33be30..0000000 --- a/test/ipams/phpipam/ipam_config +++ /dev/null @@ -1,18 +0,0 @@ -{ - 'ids' => { - 'phpipam' => { - 'url' => 'https://localhost/api/apiadmin', - 'type' => 'phpipam', - 'section' => 1, - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, - 'pve' => { - 'type' => 'pve' - }, - 'netbox' => { - 'token' => '0123456789abcdef0123456789abcdef01234567', - 'type' => 'netbox', - 'url' => 'http://localhost:8000/api' - } - }, -} diff --git a/test/ipams/phpipam/sdn_config b/test/ipams/phpipam/sdn_config deleted file mode 100644 index c774807..0000000 --- a/test/ipams/phpipam/sdn_config +++ /dev/null @@ -1,20 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "phpipam" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} diff --git a/test/run_test_dns.pl b/test/run_test_dns.pl deleted file mode 100755 index 87e011e..0000000 --- a/test/run_test_dns.pl +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use lib qw(..); -use File::Slurp; -use Net::IP; - -use Test::More; -use Test::MockModule; - -use PVE::Network::SDN; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use JSON; - -use Data::Dumper qw(Dumper); -$Data::Dumper::Sortkeys = 1; - -sub read_sdn_config { - my ($file) = @_; - # Read structure back in again - open my $in, '<', $file or die $!; - my $sdn_config; - { - local $/; # slurp mode - $sdn_config = eval <$in>; - } - close $in; - - return $sdn_config; -} - - -my @plugins = read_dir( './dns/', prefix => 1 ) ; - -foreach my $path (@plugins) { - - my (undef, $dnsid) = split(/\//, $path); - my $sdn_config = read_sdn_config ("$path/sdn_config"); - - - my $pve_sdn_dns; - $pve_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns'); - $pve_sdn_dns->mock( - config => sub { - my $dns_config = read_sdn_config ("$path/dns_config"); - return $dns_config; - }, - ); - - my $sdn_module = Test::MockModule->new("PVE::Network::SDN"); - $sdn_module->mock( - config => sub { - return $sdn_config; - }, - api_request => sub { - my ($method, $url, $headers, $data) = @_; - - my $js = JSON->new; - $js->canonical(1); - - my $encoded_data = $js->encode($data) if $data; - my $req = HTTP::Request->new($method,$url, $headers, $encoded_data); - die Dumper($req); - } - ); - - - - my $dns_cfg = PVE::Network::SDN::Dns::config(); - my $plugin_config = $dns_cfg->{ids}->{$dnsid}; - my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); - - #test params; - my @ips = ("10.0.0.1", "2001:4860:4860::8888"); - my $zone = "domain.com"; - my $hostname = "myhostname"; - - foreach my $ip (@ips) { - - my $ipversion = Net::IP::ip_is_ipv6($ip) ? "ipv6" : "ipv4"; - my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A"; - my $ip2 = $type eq 'AAAA' ? '2001:4860:4860::8844' : '127.0.0.1'; - my $fqdn = $hostname.".".$zone."."; - - my $sdn_dns_plugin = Test::MockModule->new($plugin); - $sdn_dns_plugin->mock( - - get_zone_content => sub { - return undef; - }, - get_zone_rrset => sub { - return undef; - } - ); - - ## add_a_record - my $test = "add_a_record"; - my $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); - my $name = "$dnsid $test"; - - $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - ## add_ptr_record - $test = "add_ptr_record"; - $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); - $name = "$dnsid $test"; - - $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - - ## del_ptr_record - $test = "del_ptr_record"; - $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); - $name = "$dnsid $test"; - - $plugin->del_ptr_record($plugin_config, $zone, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - - ## del_a_record - - $sdn_dns_plugin->mock( - - get_zone_content => sub { - return undef; - }, - get_zone_rrset => sub { - - 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 => '3600', - records => [ $record ] }; - return $rrset; - } - ); - - $test = "del_a_record"; - $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); - $name = "$dnsid $test"; - - $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - ## del_a_multiple_record - - $sdn_dns_plugin->mock( - - get_zone_content => sub { - return undef; - }, - get_zone_rrset => sub { - - my $record = { content => $ip, - disabled => JSON::false, - name => $fqdn, - type => $type, - priority => 0 }; - - my $record2 = { content => $ip2, - disabled => JSON::false, - name => $fqdn, - type => $type, - priority => 0 }; - - my $rrset = { name => $fqdn, - type => $type, - ttl => '3600', - records => [ $record, $record2 ] }; - return $rrset; - } - ); - - $test = "del_a_multiple_record"; - $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); - $name = "$dnsid $test"; - - $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - ## add_a_multiple_record - - $sdn_dns_plugin->mock( - - get_zone_content => sub { - return undef; - }, - get_zone_rrset => sub { - - my $record2 = { content => $ip2, - disabled => JSON::false, - name => $fqdn, - type => $type, - priority => 0 }; - - my $rrset = { name => $fqdn, - type => $type, - ttl => '3600', - records => [ $record2 ] }; - return $rrset; - } - ); - - $test = "add_a_multiple_record"; - $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion"); - $name = "$dnsid $test"; - - $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - } - - ## verify_zone - my $test = "verify_zone"; - my $expected = Dumper read_sdn_config("$path/expected.$test"); - my $name = "$dnsid $test"; - - $plugin->verify_zone($plugin_config, $zone, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - -} - -done_testing(); - - diff --git a/test/run_test_ipams.pl b/test/run_test_ipams.pl deleted file mode 100755 index 27bd441..0000000 --- a/test/run_test_ipams.pl +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use lib qw(..); -use File::Slurp; - -use Test::More; -use Test::MockModule; - -use PVE::Network::SDN; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use PVE::INotify; -use JSON; - -use Data::Dumper qw(Dumper); -$Data::Dumper::Sortkeys = 1; - -sub read_sdn_config { - my ($file) = @_; - # Read structure back in again - open my $in, '<', $file or die $!; - my $sdn_config; - { - local $/; # slurp mode - $sdn_config = eval <$in>; - } - close $in; - - return $sdn_config; -} - - -#my @plugins = <./ipams/*>; -my @plugins = read_dir( './ipams/', prefix => 1 ) ; - -foreach my $path (@plugins) { - - my (undef, $ipamid) = split(/\//, $path); - my $sdn_config = read_sdn_config ("$path/sdn_config"); - - - my $pve_sdn_subnets; - $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); - $pve_sdn_subnets->mock( - config => sub { - return $sdn_config->{subnets}; - }, - ); - - my $pve_sdn_ipam; - $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Ipams'); - $pve_sdn_subnets->mock( - config => sub { - my $ipam_config = read_sdn_config ("$path/ipam_config"); - return $ipam_config; - }, - ); - - my $sdn_module = Test::MockModule->new("PVE::Network::SDN"); - $sdn_module->mock( - config => sub { - return $sdn_config; - }, - api_request => sub { - my ($method, $url, $headers, $data) = @_; - - my $js = JSON->new; - $js->canonical(1); - - my $encoded_data = $js->encode($data) if $data; - my $req = HTTP::Request->new($method,$url, $headers, $encoded_data); - die Dumper($req); - } - ); - - - - #test params; - my $subnetid = "myzone-10.0.0.0-24"; - my $ip = "10.0.0.1"; - my $hostname = "myhostname"; - my $mac = "da:65:8f:18:9b:6f"; - my $description = "mydescription"; - my $is_gateway = 1; - - - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); - - 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 $sdn_ipam_plugin = Test::MockModule->new($plugin); - $sdn_ipam_plugin->mock( - get_prefix_id => sub { - return 1; - }, - get_ip_id => sub { - return 1; - }, - is_ip_gateway => sub { - return 1; - } - ); - - ## add_ip - my $test = "add_ip"; - my $expected = Dumper read_sdn_config("$path/expected.$test"); - my $name = "$ipamid $test"; - - $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - ## add_next_freeip - $test = "add_next_freeip"; - $expected = Dumper read_sdn_config("$path/expected.$test"); - $name = "$ipamid $test"; - - $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - - ## del_ip - $test = "del_ip"; - $expected = Dumper read_sdn_config("$path/expected.$test"); - $name = "$ipamid $test"; - - $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - ## update_ip - $test = "update_ip"; - $expected = Dumper read_sdn_config("$path/expected.$test"); - $name = "$ipamid $test"; - $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - ## add_ip_notgateway - $is_gateway = undef; - $test = "add_ip_notgateway"; - $expected = Dumper read_sdn_config("$path/expected.$test"); - $name = "$ipamid $test"; - - $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - - $sdn_ipam_plugin->mock( - get_prefix_id => sub { - return undef; - }, - ); - - ## add_subnet - $test = "add_subnet"; - $expected = Dumper read_sdn_config("$path/expected.$test"); - $name = "$ipamid $test"; - - $plugin->add_subnet($plugin_config, $subnetid, $subnet, 1); - - if ($@) { - is ($@, $expected, $name); - } else { - fail($name); - } - -} - -done_testing(); - - diff --git a/test/run_test_subnets.pl b/test/run_test_subnets.pl deleted file mode 100755 index f6564e1..0000000 --- a/test/run_test_subnets.pl +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use lib qw(..); -use File::Slurp; - -use Test::More; -use Test::MockModule; - -use PVE::Network::SDN; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use PVE::INotify; -use JSON; - -use Data::Dumper qw(Dumper); -$Data::Dumper::Sortkeys = 1; - -sub read_sdn_config { - my ($file) = @_; - # Read structure back in again - open my $in, '<', $file or die $!; - my $sdn_config; - { - local $/; # slurp mode - $sdn_config = eval <$in>; - } - close $in; - - return $sdn_config; -} - - -my @plugins = read_dir( './subnets/', prefix => 1 ) ; - -foreach my $path (@plugins) { - - my (undef, $testid) = split(/\//, $path); - - print "test: $testid\n"; - my $sdn_config = read_sdn_config ("$path/sdn_config"); - - - my $pve_sdn_subnets; - $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); - $pve_sdn_subnets->mock( - config => sub { - return $sdn_config->{subnets}; - }, - verify_dns_zone => sub { - return; - }, - add_dns_record => sub { - return; - } - ); - - - my $js = JSON->new; - $js->canonical(1); - - - #test params; - my $subnets = $sdn_config->{subnets}->{ids}; - my $subnetid = (keys %{$subnets})[0]; - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); - - my $subnet_cidr = $subnet->{cidr}; - my $iplist = NetAddr::IP->new($subnet_cidr); - $iplist++ if Net::IP::ip_is_ipv4($iplist->canon()); #skip network address for ipv4 - my $ip = $iplist->canon(); - $iplist++; - my $ipnextfree = $iplist->canon(); - $iplist++; - my $ip2 = $iplist->canon(); - - my $ip3 = undef; - my $hostname = "myhostname"; - my $mac = "da:65:8f:18:9b:6f"; - my $description = "mydescription"; - my $is_gateway = 1; - my $ipamdb = {}; - - my $zone = $sdn_config->{zones}->{ids}->{"myzone"}; - my $ipam = $zone->{ipam}; - - my $plugin; - my $sdn_ipam_plugin; - if($ipam) { - $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam); - $sdn_ipam_plugin = Test::MockModule->new($plugin); - $sdn_ipam_plugin->mock( - read_db => sub { - return $ipamdb; - }, - write_db => sub { - my ($cfg) = @_; - $ipamdb = $cfg; - } - ); - } - - my $pve_sdn_ipams; - $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams'); - $pve_sdn_ipams->mock( - config => sub { - my $ipam_config = read_sdn_config ("$path/ipam_config"); - return $ipam_config; - }, - ); - - ## add_subnet - my $test = "add_subnet $subnetid"; - my $name = "$testid $test"; - my $result = undef; - my $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{}}}}}}'; - - eval { - PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet); - - }; - - if ($@) { - fail("$name : $@"); - } elsif($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } else { - is (undef, undef, $name); - } - - ## add_ip - $test = "add_ip $ip"; - $name = "$testid $test"; - $result = undef; - $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1}}}}}}}'; - - eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway); - }; - - if ($@) { - fail("$name : $@"); - } elsif($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } else { - is (undef, undef, $name); - } - - if($ipam) { - ## add_already_exist_ip - $test = "add_already_exist_ip $ip"; - $name = "$testid $test"; - - eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description); - }; - - if ($@) { - is (undef, undef, $name); - } else { - fail("$name : $@"); - } - } - - ## add_second_ip - $test = "add_second_ip $ip2"; - $name = "$testid $test"; - $result = undef; - $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ip2.'":{}}}}}}}'; - - eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip2, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } elsif($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } else { - is (undef, undef, $name); - } - - ## add_next_free - $test = "find_next_freeip ($ipnextfree)"; - $name = "$testid $test"; - $result = undef; - $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}'; - - eval { - $ip3 = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } elsif($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } - - ## del_ip - $test = "del_ip $ip"; - $name = "$testid $test"; - $result = undef; - $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}'; - - eval { - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname); - }; - - if ($@) { - fail("$name : $@"); - } elsif($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } else { - is (undef, undef, $name); - } - - if($ipam){ - ## del_subnet_not_empty - $test = "del_subnet_not_empty $subnetid"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet); - }; - - if ($@) { - is ($result, $expected, $name); - } else { - fail("$name : $@"); - } - } - - - ## add_ip_rollback_failing_dns - $test = "add_ip_rollback_failing_dns"; - - $pve_sdn_subnets->mock( - config => sub { - return $sdn_config->{subnets}; - }, - verify_dns_zone => sub { - return; - }, - add_dns_record => sub { - die "error add dns record"; - return; - } - ); - - $name = "$testid $test"; - $result = undef; - $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}'; - - eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description); - }; - - if ($@) { - if($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } else { - is (undef, undef, $name); - } - } else { - fail("$name : $@"); - } - - - ## del_empty_subnet - $test = "del_empty_subnet"; - $name = "$testid $test"; - $result = undef; - $expected = '{"zones":{"myzone":{"subnets":{}}}}'; - - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip2, $hostname); - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip3, $hostname); - - eval { - PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet); - }; - - if ($@) { - fail("$name : $@"); - } elsif($ipam) { - $result = $js->encode($plugin->read_db()); - is ($result, $expected, $name); - } else { - is (undef, undef, $name); - } - -} - -done_testing(); - - diff --git a/test/run_test_vnets.pl b/test/run_test_vnets.pl deleted file mode 100755 index 5aeb676..0000000 --- a/test/run_test_vnets.pl +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use lib qw(..); -use File::Slurp; -use NetAddr::IP qw(:lower); - -use Test::More; -use Test::MockModule; - -use PVE::Network::SDN; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use PVE::INotify; -use JSON; - -use Data::Dumper qw(Dumper); -$Data::Dumper::Sortkeys = 1; - -sub read_sdn_config { - my ($file) = @_; - # Read structure back in again - open my $in, '<', $file or die $!; - my $sdn_config; - { - local $/; # slurp mode - $sdn_config = eval <$in>; - } - close $in; - return $sdn_config; -} - - -my @plugins = read_dir( './vnets/', prefix => 1 ) ; - -foreach my $path (@plugins) { - - my (undef, $testid) = split(/\//, $path); - - print "test: $testid\n"; - my $sdn_config = read_sdn_config ("$path/sdn_config"); - - my $pve_sdn_zones; - $pve_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones'); - $pve_sdn_zones->mock( - config => sub { - return $sdn_config->{zones}; - }, - ); - - my $pve_sdn_vnets; - $pve_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets'); - $pve_sdn_vnets->mock( - config => sub { - return $sdn_config->{vnets}; - }, - ); - - my $pve_sdn_subnets; - $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); - $pve_sdn_subnets->mock( - config => sub { - return $sdn_config->{subnets}; - }, - verify_dns_zone => sub { - return; - }, - add_dns_record => sub { - return; - } - ); - - my $js = JSON->new; - $js->canonical(1); - - #test params; - #test params; - my $subnets = $sdn_config->{subnets}->{ids}; - - my $subnetid = (sort keys %{$subnets})[0]; - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); - my $subnet_cidr = $subnet->{cidr}; - my $iplist = NetAddr::IP->new($subnet_cidr); - my $mask = $iplist->masklen(); - my $ipversion = undef; - - if (Net::IP::ip_is_ipv4($iplist->canon())){ - $iplist++; #skip network address for ipv4 - $ipversion = 4; - } else { - $ipversion = 6; - } - - my $cidr1 = $iplist->canon()."/$mask"; - $iplist++; - my $cidr2 = $iplist->canon()."/$mask"; - my $cidr_outofrange = '8.8.8.8/8'; - - my $subnetid2 = (sort keys %{$subnets})[1]; - my $subnet2 = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid2, 1); - my $subnet2_cidr = $subnet2->{cidr}; - my $iplist2 = NetAddr::IP->new($subnet2_cidr); - $iplist2++; - my $cidr3 = $iplist2->canon()."/$mask"; - $iplist2++; - my $cidr4 = $iplist2->canon()."/$mask"; - - my $hostname = "myhostname"; - my $mac = "da:65:8f:18:9b:6f"; - my $description = "mydescription"; - my $ipamdb = read_sdn_config ("$path/ipam.db"); - - my $zone = $sdn_config->{zones}->{ids}->{"myzone"}; - my $ipam = $zone->{ipam}; - - my $plugin; - my $sdn_ipam_plugin; - if($ipam) { - $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam); - $sdn_ipam_plugin = Test::MockModule->new($plugin); - $sdn_ipam_plugin->mock( - read_db => sub { - return $ipamdb; - }, - write_db => sub { - my ($cfg) = @_; - $ipamdb = $cfg; - } - ); - } - - my $pve_sdn_ipams; - $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams'); - $pve_sdn_ipams->mock( - config => sub { - my $ipam_config = read_sdn_config ("$path/ipam_config"); - return $ipam_config; - }, - ); - - my $vnetid = "myvnet"; - - ## add_ip - my $test = "add_cidr $cidr1"; - my $name = "$testid $test"; - my $result = undef; - my $expected = ''; - - eval { - PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - ## add_ip - $test = "add_already_exist_cidr $cidr1"; - $name = "$testid $test"; - $result = undef; - $expected = ''; - - eval { - PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description); - }; - - if ($@) { - is (undef, undef, $name); - } elsif($ipam) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - ## add_ip - $test = "add_cidr $cidr2"; - $name = "$testid $test"; - $result = undef; - $expected = ''; - - eval { - PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr2, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - ## add_ip - $test = "add_ip_out_of_range_subnets $cidr_outofrange"; - $name = "$testid $test"; - $result = undef; - $expected = ''; - - eval { - PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr_outofrange, $hostname, $mac, $description); - }; - - if ($@) { - is (undef, undef, $name); - } else { - fail("$name : $@"); - } - - ## add_ip - $test = "add_cidr $cidr4"; - $name = "$testid $test"; - $result = undef; - $expected = ''; - - eval { - PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr4, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - - $test = "find_next_free_cidr_in_second_subnet ($cidr3)"; - $name = "$testid $test"; - $result = undef; - $expected = $ipam ? $cidr3 : undef; - - eval { - $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion); - }; - - if ($@) { - fail("$name : $@"); - } else { - is ($result, $expected, $name); - } - - - $test = "del_cidr $cidr1"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - $test = "del_cidr $cidr3"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr3, $hostname); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - $test = "del_cidr not exist $cidr1"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname); - }; - - if ($@) { - is (undef, undef, $name); - } elsif($ipam) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - $test = "del_cidr outofrange $cidr_outofrange"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr_outofrange, $hostname); - }; - - if ($@) { - is (undef, undef, $name); - } else { - fail("$name : $@"); - } - - $test = "find_next_free_cidr_in_first_subnet ($cidr1)"; - $name = "$testid $test"; - $result = undef; - $expected = $ipam ? $cidr1 : undef; - - eval { - $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion); - }; - - if ($@) { - fail("$name : $@"); - } else { - is ($result, $expected, $name); - } - - $test = "update_cidr $cidr1"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - - $test = "update_cidr deleted $cidr3"; - $name = "$testid $test"; - $result = undef; - $expected = undef; - - eval { - $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description); - }; - - if ($@) { - fail("$name : $@"); - } else { - is (undef, undef, $name); - } - -} - -done_testing(); - - diff --git a/test/run_test_zones.pl b/test/run_test_zones.pl deleted file mode 100755 index 12e017a..0000000 --- a/test/run_test_zones.pl +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use lib qw(..); -use File::Slurp; - -use Test::More; -use Test::MockModule; - -use PVE::Network::SDN; -use PVE::Network::SDN::Zones; -use PVE::Network::SDN::Controllers; -use PVE::INotify; - -sub read_sdn_config { - my ($file) = @_; - - # Read structure back in again - open my $in, '<', $file or die $!; - my $sdn_config; - { - local $/; # slurp mode - $sdn_config = eval <$in>; - } - close $in; - - return $sdn_config; -} - - -my @tests = grep { -d } glob './zones/*/*'; - -foreach my $test (@tests) { - - my $sdn_config = read_sdn_config ("./$test/sdn_config"); - - open my $fh1, '<', "./$test/interfaces" or die "can't read interfaces file"; - my $interfaces_config = PVE::INotify::__read_etc_network_interfaces($fh1, undef, undef); - close $fh1; - - my $pve_common_inotify; - $pve_common_inotify = Test::MockModule->new('PVE::INotify'); - $pve_common_inotify->mock( - nodename => sub { - return 'localhost'; - }, - read_file => sub { - return $interfaces_config; - }, - ); - - my $pve_sdn_subnets; - $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); - $pve_sdn_subnets->mock( - config => sub { - return $sdn_config->{subnets}; - }, - ); - - my $pve_sdn_zones_plugin; - $pve_sdn_zones_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin'); - $pve_sdn_zones_plugin->mock( - get_local_route_ip => sub { - my $outiface = "vmbr0"; - my $outip = $interfaces_config->{ifaces}->{$outiface}->{address}; - return ($outip, $outiface); - }, - is_vlanaware => sub { - return $interfaces_config->{ifaces}->{vmbr0}->{'bridge_vlan_aware'}; - }, - is_ovs => sub { - return 1 if $interfaces_config->{ifaces}->{vmbr0}->{'type'} eq 'OVSBridge'; - }, - get_bridge_ifaces => sub { - return ('eth0'); - }, - find_bridge => sub { - return; - } - ); - - my $sdn_module = Test::MockModule->new("PVE::Network::SDN"); - $sdn_module->mock( - running_config => sub { - return $sdn_config; - }, - ); - - my $name = $test; - my $expected = read_file("./$test/expected_sdn_interfaces"); - - my $result = ""; - eval { - $result = PVE::Network::SDN::Zones::generate_etc_network_config(); - }; - - if (my $err = $@) { - fail($name); - } else { - is ($result, $expected, $name); - } - - if ($sdn_config->{controllers}) { - my $expected = read_file("./$test/expected_controller_config"); - my $controller_rawconfig = ""; - - eval { - my $config = PVE::Network::SDN::Controllers::generate_controller_config(); - $controller_rawconfig = PVE::Network::SDN::Controllers::generate_controller_rawconfig($config); - }; - if (my $err = $@) { - fail($name); - } else { - is ($controller_rawconfig, $expected, $name); - } - } -} - -done_testing(); - - diff --git a/test/subnets/ipv4/ipam_config b/test/subnets/ipv4/ipam_config deleted file mode 100644 index a33be30..0000000 --- a/test/subnets/ipv4/ipam_config +++ /dev/null @@ -1,18 +0,0 @@ -{ - 'ids' => { - 'phpipam' => { - 'url' => 'https://localhost/api/apiadmin', - 'type' => 'phpipam', - 'section' => 1, - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, - 'pve' => { - 'type' => 'pve' - }, - 'netbox' => { - 'token' => '0123456789abcdef0123456789abcdef01234567', - 'type' => 'netbox', - 'url' => 'http://localhost:8000/api' - } - }, -} diff --git a/test/subnets/ipv4/sdn_config b/test/subnets/ipv4/sdn_config deleted file mode 100644 index 72697d4..0000000 --- a/test/subnets/ipv4/sdn_config +++ /dev/null @@ -1,20 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type =>"simple" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} diff --git a/test/subnets/ipv6/ipam_config b/test/subnets/ipv6/ipam_config deleted file mode 100644 index a33be30..0000000 --- a/test/subnets/ipv6/ipam_config +++ /dev/null @@ -1,18 +0,0 @@ -{ - 'ids' => { - 'phpipam' => { - 'url' => 'https://localhost/api/apiadmin', - 'type' => 'phpipam', - 'section' => 1, - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, - 'pve' => { - 'type' => 'pve' - }, - 'netbox' => { - 'token' => '0123456789abcdef0123456789abcdef01234567', - 'type' => 'netbox', - 'url' => 'http://localhost:8000/api' - } - }, -} diff --git a/test/subnets/ipv6/sdn_config b/test/subnets/ipv6/sdn_config deleted file mode 100644 index 618f234..0000000 --- a/test/subnets/ipv6/sdn_config +++ /dev/null @@ -1,20 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type =>"simple" } }, - }, - - subnets => { - ids => { 'myzone-2a0a:1580:2000::-56' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} diff --git a/test/subnets/noipam/ipam_config b/test/subnets/noipam/ipam_config deleted file mode 100644 index a33be30..0000000 --- a/test/subnets/noipam/ipam_config +++ /dev/null @@ -1,18 +0,0 @@ -{ - 'ids' => { - 'phpipam' => { - 'url' => 'https://localhost/api/apiadmin', - 'type' => 'phpipam', - 'section' => 1, - 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw' - }, - 'pve' => { - 'type' => 'pve' - }, - 'netbox' => { - 'token' => '0123456789abcdef0123456789abcdef01234567', - 'type' => 'netbox', - 'url' => 'http://localhost:8000/api' - } - }, -} diff --git a/test/subnets/noipam/sdn_config b/test/subnets/noipam/sdn_config deleted file mode 100644 index 55107d6..0000000 --- a/test/subnets/noipam/sdn_config +++ /dev/null @@ -1,20 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { type =>"simple" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} diff --git a/test/vnets/ipv4/ipam.db b/test/vnets/ipv4/ipam.db deleted file mode 100644 index ef3fa93..0000000 --- a/test/vnets/ipv4/ipam.db +++ /dev/null @@ -1,17 +0,0 @@ -{ - "zones" => { - "myzone" => { - "subnets" => { - "192.168.0.0/30" => { - "ips" =>{ - } - }, - "192.168.1.0/30" => { - "ips" =>{ - } - }, - } - } - } -} - diff --git a/test/vnets/ipv4/ipam_config b/test/vnets/ipv4/ipam_config deleted file mode 100644 index f5f36ad..0000000 --- a/test/vnets/ipv4/ipam_config +++ /dev/null @@ -1,7 +0,0 @@ -{ - 'ids' => { - 'pve' => { - 'type' => 'pve' - }, - }, -} diff --git a/test/vnets/ipv4/sdn_config b/test/vnets/ipv4/sdn_config deleted file mode 100644 index ee11fd1..0000000 --- a/test/vnets/ipv4/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type =>"simple" } }, - }, - - subnets => { - ids => { - 'myzone-192.168.0.0-30' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - 'myzone-192.168.1.0-30' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - } - - } -} diff --git a/test/vnets/ipv4noipam/ipam.db b/test/vnets/ipv4noipam/ipam.db deleted file mode 100644 index ef3fa93..0000000 --- a/test/vnets/ipv4noipam/ipam.db +++ /dev/null @@ -1,17 +0,0 @@ -{ - "zones" => { - "myzone" => { - "subnets" => { - "192.168.0.0/30" => { - "ips" =>{ - } - }, - "192.168.1.0/30" => { - "ips" =>{ - } - }, - } - } - } -} - diff --git a/test/vnets/ipv4noipam/ipam_config b/test/vnets/ipv4noipam/ipam_config deleted file mode 100644 index f5f36ad..0000000 --- a/test/vnets/ipv4noipam/ipam_config +++ /dev/null @@ -1,7 +0,0 @@ -{ - 'ids' => { - 'pve' => { - 'type' => 'pve' - }, - }, -} diff --git a/test/vnets/ipv4noipam/sdn_config b/test/vnets/ipv4noipam/sdn_config deleted file mode 100644 index 470c1ae..0000000 --- a/test/vnets/ipv4noipam/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { type =>"simple" } }, - }, - - subnets => { - ids => { - 'myzone-192.168.0.0-30' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - 'myzone-192.168.1.0-30' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - } - - } -} diff --git a/test/vnets/ipv6/ipam.db b/test/vnets/ipv6/ipam.db deleted file mode 100644 index d3f2ce9..0000000 --- a/test/vnets/ipv6/ipam.db +++ /dev/null @@ -1,16 +0,0 @@ -{ - "zones" => { - "myzone" => { - "subnets" => { - "2001:db8:85a3::8a2e:370:7334/127" => { - "ips" =>{ - } - }, - "2001:db8:85a3::8a2e:371:7334/127" => { - "ips" =>{ - } - }, - } - } - } -} diff --git a/test/vnets/ipv6/ipam_config b/test/vnets/ipv6/ipam_config deleted file mode 100644 index f5f36ad..0000000 --- a/test/vnets/ipv6/ipam_config +++ /dev/null @@ -1,7 +0,0 @@ -{ - 'ids' => { - 'pve' => { - 'type' => 'pve' - }, - }, -} diff --git a/test/vnets/ipv6/sdn_config b/test/vnets/ipv6/sdn_config deleted file mode 100644 index 231ca8a..0000000 --- a/test/vnets/ipv6/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type =>"simple" } }, - }, - - subnets => { - ids => { - 'myzone-2001:db8:85a3::8a2e:370:7334-127' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - 'myzone-2001:db8:85a3::8a2e:371:7334-127' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - } - - } -} diff --git a/test/zones/evpn/advertise_subnets/expected_controller_config b/test/zones/evpn/advertise_subnets/expected_controller_config deleted file mode 100644 index 82b06b4..0000000 --- a/test/zones/evpn/advertise_subnets/expected_controller_config +++ /dev/null @@ -1,54 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family ipv4 unicast - redistribute connected - exit-address-family - ! - address-family ipv6 unicast - redistribute connected - exit-address-family - ! - address-family l2vpn evpn - advertise ipv4 unicast - advertise ipv6 unicast - exit-address-family -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/advertise_subnets/expected_sdn_interfaces b/test/zones/evpn/advertise_subnets/expected_sdn_interfaces deleted file mode 100644 index 9d1c64c..0000000 --- a/test/zones/evpn/advertise_subnets/expected_sdn_interfaces +++ /dev/null @@ -1,42 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/advertise_subnets/interfaces b/test/zones/evpn/advertise_subnets/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/advertise_subnets/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/advertise_subnets/sdn_config b/test/zones/evpn/advertise_subnets/sdn_config deleted file mode 100644 index 76f16a1..0000000 --- a/test/zones/evpn/advertise_subnets/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'advertise-subnets' => 1 } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config b/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config deleted file mode 100644 index bd7830a..0000000 --- a/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config +++ /dev/null @@ -1,41 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces b/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces deleted file mode 100644 index bbde906..0000000 --- a/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces +++ /dev/null @@ -1,40 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - mtu 1450 diff --git a/test/zones/evpn/disable_arp_nd_suppression/interfaces b/test/zones/evpn/disable_arp_nd_suppression/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/disable_arp_nd_suppression/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/disable_arp_nd_suppression/sdn_config b/test/zones/evpn/disable_arp_nd_suppression/sdn_config deleted file mode 100644 index 199596b..0000000 --- a/test/zones/evpn/disable_arp_nd_suppression/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'disable-arp-nd-suppression' => 1 } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/evpn/ebgp/expected_controller_config b/test/zones/evpn/ebgp/expected_controller_config deleted file mode 100644 index ccc0b28..0000000 --- a/test/zones/evpn/ebgp/expected_controller_config +++ /dev/null @@ -1,58 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65001 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as external - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - neighbor BGP peer-group - neighbor BGP remote-as external - neighbor BGP bfd - neighbor BGP ebgp-multihop 3 - neighbor 192.168.0.252 peer-group BGP - neighbor 192.168.0.253 peer-group BGP - ! - address-family ipv4 unicast - neighbor BGP activate - neighbor BGP soft-reconfiguration inbound - exit-address-family - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - autort as 65000 - exit-address-family -exit -! -router bgp 65001 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family l2vpn evpn - route-target import 65000:1000 - route-target export 65000:1000 - exit-address-family -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/ebgp/expected_sdn_interfaces b/test/zones/evpn/ebgp/expected_sdn_interfaces deleted file mode 100644 index 4cf13e0..0000000 --- a/test/zones/evpn/ebgp/expected_sdn_interfaces +++ /dev/null @@ -1,41 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/ebgp/interfaces b/test/zones/evpn/ebgp/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/ebgp/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/ebgp/sdn_config b/test/zones/evpn/ebgp/sdn_config deleted file mode 100644 index 6e9d116..0000000 --- a/test/zones/evpn/ebgp/sdn_config +++ /dev/null @@ -1,50 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { - tag => "100", - type => "vnet", - zone => "myzone", - }, - }, - }, - - zones => { - ids => { - myzone => { - ipam => "pve", - type => "evpn", - controller => "evpnctl", - 'vrf-vxlan' => 1000, - }, - }, - }, - controllers => { - ids => { - evpnctl => { - type => "evpn", - 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', - asn => "65000", - }, - localhost => { - type => "bgp", - 'peers' => '192.168.0.252,192.168.0.253', - ebgp => "1", - 'ebgp-multihop' => '3', - asn => "65001", - node => "localhost", - }, - }, - }, - - subnets => { - ids => { - 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - }, - }, - }, -} diff --git a/test/zones/evpn/ebgp_loopback/expected_controller_config b/test/zones/evpn/ebgp_loopback/expected_controller_config deleted file mode 100644 index 548d532..0000000 --- a/test/zones/evpn/ebgp_loopback/expected_controller_config +++ /dev/null @@ -1,69 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -ip protocol bgp route-map correct_src -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65001 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as external - neighbor VTEP bfd - neighbor VTEP ebgp-multihop 10 - neighbor VTEP update-source dummy1 - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - bgp disable-ebgp-connected-route-check - neighbor BGP peer-group - neighbor BGP remote-as external - neighbor BGP bfd - neighbor 172.16.0.254 peer-group BGP - neighbor 172.17.0.254 peer-group BGP - ! - address-family ipv4 unicast - network 192.168.0.1/32 - neighbor BGP activate - neighbor BGP soft-reconfiguration inbound - exit-address-family - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - autort as 65000 - exit-address-family -exit -! -router bgp 65001 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family l2vpn evpn - route-target import 65000:1000 - route-target export 65000:1000 - exit-address-family -exit -! -ip prefix-list loopbacks_ips seq 10 permit 0.0.0.0/0 le 32 -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -route-map correct_src permit 1 - match ip address prefix-list loopbacks_ips - set src 192.168.0.1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces b/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces deleted file mode 100644 index 4cf13e0..0000000 --- a/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces +++ /dev/null @@ -1,41 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/ebgp_loopback/interfaces b/test/zones/evpn/ebgp_loopback/interfaces deleted file mode 100644 index f6bc352..0000000 --- a/test/zones/evpn/ebgp_loopback/interfaces +++ /dev/null @@ -1,13 +0,0 @@ -auto eth0 -iface eth0 inet static - address 172.16.0.1/24 - -auto eth1 -iface eth1 inet static - address 172.17.0.1/24 - -auto dummy1 -iface dummy1 inet static - address 192.168.0.1/32 - link-type dummy - diff --git a/test/zones/evpn/ebgp_loopback/sdn_config b/test/zones/evpn/ebgp_loopback/sdn_config deleted file mode 100644 index c8bc2e0..0000000 --- a/test/zones/evpn/ebgp_loopback/sdn_config +++ /dev/null @@ -1,29 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000 } }, - }, - controllers => { - ids => { - evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" }, - localhost => { type => "bgp", 'peers' => '172.16.0.254,172.17.0.254', ebgp => "1", asn => "65001", loopback => 'dummy1', node => "localhost" }, - }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/evpn/exitnode/expected_controller_config b/test/zones/evpn/exitnode/expected_controller_config deleted file mode 100644 index 48830a3..0000000 --- a/test/zones/evpn/exitnode/expected_controller_config +++ /dev/null @@ -1,66 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family ipv4 unicast - import vrf vrf_myzone - exit-address-family - ! - address-family ipv6 unicast - import vrf vrf_myzone - exit-address-family - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family ipv4 unicast - redistribute connected - exit-address-family - ! - address-family ipv6 unicast - redistribute connected - exit-address-family - ! - address-family l2vpn evpn - default-originate ipv4 - default-originate ipv6 - exit-address-family -exit -! -route-map MAP_VTEP_IN deny 1 - match evpn route-type prefix -exit -! -route-map MAP_VTEP_IN permit 2 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/exitnode/expected_sdn_interfaces b/test/zones/evpn/exitnode/expected_sdn_interfaces deleted file mode 100644 index 5ab3084..0000000 --- a/test/zones/evpn/exitnode/expected_sdn_interfaces +++ /dev/null @@ -1,41 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/exitnode/interfaces b/test/zones/evpn/exitnode/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/exitnode/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/exitnode/sdn_config b/test/zones/evpn/exitnode/sdn_config deleted file mode 100644 index fd81817..0000000 --- a/test/zones/evpn/exitnode/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/evpn/exitnode_local_routing/expected_controller_config b/test/zones/evpn/exitnode_local_routing/expected_controller_config deleted file mode 100644 index f671b63..0000000 --- a/test/zones/evpn/exitnode_local_routing/expected_controller_config +++ /dev/null @@ -1,51 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -ip route 10.0.0.0/24 10.255.255.2 xvrf_myzone -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family l2vpn evpn - default-originate ipv4 - default-originate ipv6 - exit-address-family -exit -! -route-map MAP_VTEP_IN deny 1 - match evpn route-type prefix -exit -! -route-map MAP_VTEP_IN permit 2 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces b/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces deleted file mode 100644 index 301f5b3..0000000 --- a/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces +++ /dev/null @@ -1,56 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto xvrf_myzone -iface xvrf_myzone - link-type veth - address 10.255.255.1/30 - veth-peer-name xvrfp_myzone - mtu 1500 - -auto xvrfp_myzone -iface xvrfp_myzone - link-type veth - address 10.255.255.2/30 - veth-peer-name xvrf_myzone - vrf vrf_myzone - mtu 1500 diff --git a/test/zones/evpn/exitnode_local_routing/interfaces b/test/zones/evpn/exitnode_local_routing/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/exitnode_local_routing/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/exitnode_local_routing/sdn_config b/test/zones/evpn/exitnode_local_routing/sdn_config deleted file mode 100644 index f5f7ca1..0000000 --- a/test/zones/evpn/exitnode_local_routing/sdn_config +++ /dev/null @@ -1,27 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 }, 'exitnodes-local-routing' => 1 }, - }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - }, - } - } -} - - diff --git a/test/zones/evpn/exitnode_primary/expected_controller_config b/test/zones/evpn/exitnode_primary/expected_controller_config deleted file mode 100644 index e45b22c..0000000 --- a/test/zones/evpn/exitnode_primary/expected_controller_config +++ /dev/null @@ -1,68 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family ipv4 unicast - import vrf vrf_myzone - exit-address-family - ! - address-family ipv6 unicast - import vrf vrf_myzone - exit-address-family - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family ipv4 unicast - redistribute connected - exit-address-family - ! - address-family ipv6 unicast - redistribute connected - exit-address-family - ! - address-family l2vpn evpn - default-originate ipv4 - default-originate ipv6 - exit-address-family -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 - match evpn vni 1000 - match evpn route-type prefix - set metric 200 -exit -! -route-map MAP_VTEP_OUT permit 2 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/exitnode_primary/expected_sdn_interfaces b/test/zones/evpn/exitnode_primary/expected_sdn_interfaces deleted file mode 100644 index 5ab3084..0000000 --- a/test/zones/evpn/exitnode_primary/expected_sdn_interfaces +++ /dev/null @@ -1,41 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/exitnode_primary/interfaces b/test/zones/evpn/exitnode_primary/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/exitnode_primary/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/exitnode_primary/sdn_config b/test/zones/evpn/exitnode_primary/sdn_config deleted file mode 100644 index bfeafc5..0000000 --- a/test/zones/evpn/exitnode_primary/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'exitnodes-primary' => "othernode", exitnodes => { 'localhost' => 1 } } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/evpn/exitnode_snat/expected_controller_config b/test/zones/evpn/exitnode_snat/expected_controller_config deleted file mode 100644 index 48830a3..0000000 --- a/test/zones/evpn/exitnode_snat/expected_controller_config +++ /dev/null @@ -1,66 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family ipv4 unicast - import vrf vrf_myzone - exit-address-family - ! - address-family ipv6 unicast - import vrf vrf_myzone - exit-address-family - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family ipv4 unicast - redistribute connected - exit-address-family - ! - address-family ipv6 unicast - redistribute connected - exit-address-family - ! - address-family l2vpn evpn - default-originate ipv4 - default-originate ipv6 - exit-address-family -exit -! -route-map MAP_VTEP_IN deny 1 - match evpn route-type prefix -exit -! -route-map MAP_VTEP_IN permit 2 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/exitnode_snat/expected_sdn_interfaces b/test/zones/evpn/exitnode_snat/expected_sdn_interfaces deleted file mode 100644 index 47df77a..0000000 --- a/test/zones/evpn/exitnode_snat/expected_sdn_interfaces +++ /dev/null @@ -1,68 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 - post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto myvnet2 -iface myvnet2 - address 2a08:2142:302:3::1/64 - post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 - post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 - bridge_ports vxlan_myvnet2 - bridge_stp off - bridge_fd 0 - mtu 1450 - ip6-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route del vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet2 -iface vxlan_myvnet2 - vxlan-id 200 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/exitnode_snat/interfaces b/test/zones/evpn/exitnode_snat/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/exitnode_snat/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/exitnode_snat/sdn_config b/test/zones/evpn/exitnode_snat/sdn_config deleted file mode 100644 index 35cdf5d..0000000 --- a/test/zones/evpn/exitnode_snat/sdn_config +++ /dev/null @@ -1,35 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - myvnet2 => { tag => "200", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { - 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - 'snat' => 1 - }, - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet2', - 'gateway' => '2a08:2142:302:3::1', - 'snat' => 1 - } - } - } -} - - diff --git a/test/zones/evpn/ipv4/expected_controller_config b/test/zones/evpn/ipv4/expected_controller_config deleted file mode 100644 index bd7830a..0000000 --- a/test/zones/evpn/ipv4/expected_controller_config +++ /dev/null @@ -1,41 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/ipv4/expected_sdn_interfaces b/test/zones/evpn/ipv4/expected_sdn_interfaces deleted file mode 100644 index 9d1c64c..0000000 --- a/test/zones/evpn/ipv4/expected_sdn_interfaces +++ /dev/null @@ -1,42 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/ipv4/interfaces b/test/zones/evpn/ipv4/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/ipv4/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/ipv4/sdn_config b/test/zones/evpn/ipv4/sdn_config deleted file mode 100644 index dd73b5c..0000000 --- a/test/zones/evpn/ipv4/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/evpn/ipv4ipv6/expected_controller_config b/test/zones/evpn/ipv4ipv6/expected_controller_config deleted file mode 100644 index bd7830a..0000000 --- a/test/zones/evpn/ipv4ipv6/expected_controller_config +++ /dev/null @@ -1,41 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces b/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces deleted file mode 100644 index 7a5d741..0000000 --- a/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces +++ /dev/null @@ -1,44 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - address 2a08:2142:302:3::1/64 - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - ip6-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/ipv4ipv6/interfaces b/test/zones/evpn/ipv4ipv6/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/ipv4ipv6/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/ipv4ipv6/sdn_config b/test/zones/evpn/ipv4ipv6/sdn_config deleted file mode 100644 index 4583818..0000000 --- a/test/zones/evpn/ipv4ipv6/sdn_config +++ /dev/null @@ -1,32 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { - 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - }, - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '2a08:2142:302:3::1', - } - } - } -} - - diff --git a/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config b/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config deleted file mode 100644 index bd7830a..0000000 --- a/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config +++ /dev/null @@ -1,41 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces b/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces deleted file mode 100644 index 378fa77..0000000 --- a/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces +++ /dev/null @@ -1,40 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/ipv4ipv6nogateway/interfaces b/test/zones/evpn/ipv4ipv6nogateway/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/ipv4ipv6nogateway/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/ipv4ipv6nogateway/sdn_config b/test/zones/evpn/ipv4ipv6nogateway/sdn_config deleted file mode 100644 index ab2273f..0000000 --- a/test/zones/evpn/ipv4ipv6nogateway/sdn_config +++ /dev/null @@ -1,30 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { - 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} - - diff --git a/test/zones/evpn/ipv6/expected_controller_config b/test/zones/evpn/ipv6/expected_controller_config deleted file mode 100644 index bd7830a..0000000 --- a/test/zones/evpn/ipv6/expected_controller_config +++ /dev/null @@ -1,41 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/ipv6/expected_sdn_interfaces b/test/zones/evpn/ipv6/expected_sdn_interfaces deleted file mode 100644 index b2bdbfe..0000000 --- a/test/zones/evpn/ipv6/expected_sdn_interfaces +++ /dev/null @@ -1,42 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 2a08:2142:302:3::1/64 - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip6-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/ipv6/interfaces b/test/zones/evpn/ipv6/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/ipv6/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/ipv6/sdn_config b/test/zones/evpn/ipv6/sdn_config deleted file mode 100644 index 949e886..0000000 --- a/test/zones/evpn/ipv6/sdn_config +++ /dev/null @@ -1,27 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '2a08:2142:302:3::1', - } - } - } -} - - diff --git a/test/zones/evpn/multipath_relax/expected_controller_config b/test/zones/evpn/multipath_relax/expected_controller_config deleted file mode 100644 index 2d1ad44..0000000 --- a/test/zones/evpn/multipath_relax/expected_controller_config +++ /dev/null @@ -1,53 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - bgp bestpath as-path multipath-relax - neighbor BGP peer-group - neighbor BGP remote-as 65000 - neighbor BGP bfd - neighbor 192.168.0.1 peer-group BGP - neighbor 192.168.0.2 peer-group BGP - neighbor 192.168.0.3 peer-group BGP - ! - address-family ipv4 unicast - neighbor BGP activate - neighbor BGP soft-reconfiguration inbound - exit-address-family - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/multipath_relax/expected_sdn_interfaces b/test/zones/evpn/multipath_relax/expected_sdn_interfaces deleted file mode 100644 index 4cf13e0..0000000 --- a/test/zones/evpn/multipath_relax/expected_sdn_interfaces +++ /dev/null @@ -1,41 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/multipath_relax/interfaces b/test/zones/evpn/multipath_relax/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/multipath_relax/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/multipath_relax/sdn_config b/test/zones/evpn/multipath_relax/sdn_config deleted file mode 100644 index 5a1d8a7..0000000 --- a/test/zones/evpn/multipath_relax/sdn_config +++ /dev/null @@ -1,49 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { - tag => "100", - type => "vnet", - zone => "myzone", - }, - }, - }, - - zones => { - ids => { - myzone => { - ipam => "pve", - type => "evpn", - controller => "evpnctl", - 'vrf-vxlan' => 1000, - }, - }, - }, - controllers => { - ids => { - evpnctl => { - type => "evpn", - 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', - asn => "65000", - }, - localhost => { - type => "bgp", - 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', - 'bgp-multipath-as-path-relax' => "1", - asn => "65000", - node => "localhost", - }, - }, - }, - - subnets => { - ids => { - 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - }, - }, - }, -} diff --git a/test/zones/evpn/rt_import/expected_controller_config b/test/zones/evpn/rt_import/expected_controller_config deleted file mode 100644 index f4f28dd..0000000 --- a/test/zones/evpn/rt_import/expected_controller_config +++ /dev/null @@ -1,47 +0,0 @@ -frr version 8.2.2 -frr defaults datacenter -hostname localhost -log syslog informational -service integrated-vtysh-config -! -! -vrf vrf_myzone - vni 1000 -exit-vrf -! -router bgp 65000 - bgp router-id 192.168.0.1 - no bgp default ipv4-unicast - coalesce-time 1000 - neighbor VTEP peer-group - neighbor VTEP remote-as 65000 - neighbor VTEP bfd - neighbor 192.168.0.2 peer-group VTEP - neighbor 192.168.0.3 peer-group VTEP - ! - address-family l2vpn evpn - neighbor VTEP route-map MAP_VTEP_IN in - neighbor VTEP route-map MAP_VTEP_OUT out - neighbor VTEP activate - advertise-all-vni - exit-address-family -exit -! -router bgp 65000 vrf vrf_myzone - bgp router-id 192.168.0.1 - ! - address-family l2vpn evpn - route-target import 65001:1000 - route-target import 65002:1000 - route-target import 65003:1000 - exit-address-family -exit -! -route-map MAP_VTEP_IN permit 1 -exit -! -route-map MAP_VTEP_OUT permit 1 -exit -! -line vty -! \ No newline at end of file diff --git a/test/zones/evpn/rt_import/expected_sdn_interfaces b/test/zones/evpn/rt_import/expected_sdn_interfaces deleted file mode 100644 index 9d1c64c..0000000 --- a/test/zones/evpn/rt_import/expected_sdn_interfaces +++ /dev/null @@ -1,42 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - hwaddress A2:1D:CB:1A:C0:8B - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - ip-forward on - arp-accept on - vrf vrf_myzone - -auto vrf_myzone -iface vrf_myzone - vrf-table auto - post-up ip route add vrf vrf_myzone unreachable default metric 4278198272 - -auto vrfbr_myzone -iface vrfbr_myzone - bridge-ports vrfvx_myzone - bridge_stp off - bridge_fd 0 - mtu 1450 - vrf vrf_myzone - -auto vrfvx_myzone -iface vrfvx_myzone - vxlan-id 1000 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan-local-tunnelip 192.168.0.1 - bridge-learning off - bridge-arp-nd-suppress on - mtu 1450 diff --git a/test/zones/evpn/rt_import/interfaces b/test/zones/evpn/rt_import/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/evpn/rt_import/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/evpn/rt_import/sdn_config b/test/zones/evpn/rt_import/sdn_config deleted file mode 100644 index b62bb2e..0000000 --- a/test/zones/evpn/rt_import/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", zone => "myzone" }, - }, - }, - - zones => { - ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'rt-import' => '65001:1000,65002:1000,65003:1000' } }, - }, - controllers => { - ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - } - } - } -} - - diff --git a/test/zones/qinq/bridge/expected_sdn_interfaces b/test/zones/qinq/bridge/expected_sdn_interfaces deleted file mode 100644 index 58a0e23..0000000 --- a/test/zones/qinq/bridge/expected_sdn_interfaces +++ /dev/null @@ -1,65 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto ln_myzone2 -iface ln_myzone2 - link-type veth - veth-peer-name pr_myzone2 - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto myvnet2 -iface myvnet2 - bridge_ports z_myzone.101 - bridge_stp off - bridge_fd 0 - -auto myvnet3 -iface myvnet3 - bridge_ports z_myzone2.100 - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto pr_myzone2 -iface pr_myzone2 - link-type veth - veth-peer-name ln_myzone2 - -auto sv_myzone -iface sv_myzone - vlan-raw-device eth0 - vlan-id 10 - -auto sv_myzone2 -iface sv_myzone2 - vlan-raw-device eth0 - vlan-id 20 - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto z_myzone2 -iface z_myzone2 - bridge-stp off - bridge-ports sv_myzone2 ln_myzone2 - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge/interfaces b/test/zones/qinq/bridge/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/qinq/bridge/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/qinq/bridge/sdn_config b/test/zones/qinq/bridge/sdn_config deleted file mode 100644 index 6321603..0000000 --- a/test/zones/qinq/bridge/sdn_config +++ /dev/null @@ -1,16 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - myvnet2 => { tag => 101, type => "vnet", zone => "myzone" }, - myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" }, - }, - }, - zones => { - ids => { - myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" }, - myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" }, - }, - }, -} diff --git a/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces b/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces deleted file mode 100644 index cfa43a2..0000000 --- a/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces +++ /dev/null @@ -1,36 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto myvnet2 -iface myvnet2 - bridge_ports pr_myzone - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto sv_myzone -iface sv_myzone - vlan-raw-device eth0 - vlan-id 10 - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_notagvnet/interfaces b/test/zones/qinq/bridge_notagvnet/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/qinq/bridge_notagvnet/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/qinq/bridge_notagvnet/sdn_config b/test/zones/qinq/bridge_notagvnet/sdn_config deleted file mode 100644 index 1f40369..0000000 --- a/test/zones/qinq/bridge_notagvnet/sdn_config +++ /dev/null @@ -1,26 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { - tag => 100, - type => "vnet", - zone => "myzone" - }, - myvnet2 => { - type => "vnet", - zone => "myzone" - }, - }, - }, - zones => { - ids => { - myzone => { - bridge => "vmbr0", - tag => 10, - ipam => "pve", - type => "qinq", - }, - }, - }, -} diff --git a/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces b/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces deleted file mode 100644 index c325dec..0000000 --- a/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces +++ /dev/null @@ -1,55 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto ln_myzone2 -iface ln_myzone2 - link-type veth - veth-peer-name pr_myzone2 - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto myvnet2 -iface myvnet2 - bridge_ports z_myzone.101 - bridge_stp off - bridge_fd 0 - -auto myvnet3 -iface myvnet3 - bridge_ports z_myzone2.100 - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto pr_myzone2 -iface pr_myzone2 - link-type veth - veth-peer-name ln_myzone2 - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports vmbr0.10 ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto z_myzone2 -iface z_myzone2 - bridge-stp off - bridge-ports vmbr0.20 ln_myzone2 - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_vlanaware/interfaces b/test/zones/qinq/bridge_vlanaware/interfaces deleted file mode 100644 index cfdfafe..0000000 --- a/test/zones/qinq/bridge_vlanaware/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 - bridge-vids 2-4094 - bridge-vlan-aware 1 diff --git a/test/zones/qinq/bridge_vlanaware/sdn_config b/test/zones/qinq/bridge_vlanaware/sdn_config deleted file mode 100644 index 6321603..0000000 --- a/test/zones/qinq/bridge_vlanaware/sdn_config +++ /dev/null @@ -1,16 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - myvnet2 => { tag => 101, type => "vnet", zone => "myzone" }, - myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" }, - }, - }, - zones => { - ids => { - myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" }, - myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" }, - }, - }, -} diff --git a/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces b/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces deleted file mode 100644 index cd87a3a..0000000 --- a/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces +++ /dev/null @@ -1,27 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports pr_myzone - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports vmbr0.10 ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces b/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces deleted file mode 100644 index cfdfafe..0000000 --- a/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 - bridge-vids 2-4094 - bridge-vlan-aware 1 diff --git a/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config b/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config deleted file mode 100644 index 2382f4d..0000000 --- a/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces b/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces deleted file mode 100644 index 28d215b..0000000 --- a/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces +++ /dev/null @@ -1,27 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports vmbr0.10 ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces b/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces deleted file mode 100644 index cfdfafe..0000000 --- a/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 - bridge-vids 2-4094 - bridge-vlan-aware 1 diff --git a/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config b/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config deleted file mode 100644 index c013176..0000000 --- a/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces b/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces deleted file mode 100644 index 0bc301b..0000000 --- a/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces +++ /dev/null @@ -1,29 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto vmbr0 -iface vmbr0 - bridge-vlan-protocol 802.1ad - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports vmbr0.10 ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces b/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces deleted file mode 100644 index cfdfafe..0000000 --- a/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 - bridge-vids 2-4094 - bridge-vlan-aware 1 diff --git a/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config b/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config deleted file mode 100644 index 20a8a51..0000000 --- a/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces b/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces deleted file mode 100644 index bde23d9..0000000 --- a/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces +++ /dev/null @@ -1,32 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto sv_myzone -iface sv_myzone - vlan-raw-device eth0 - vlan-id 10 - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_vlanawarevnet/interfaces b/test/zones/qinq/bridge_vlanawarevnet/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/qinq/bridge_vlanawarevnet/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/qinq/bridge_vlanawarevnet/sdn_config b/test/zones/qinq/bridge_vlanawarevnet/sdn_config deleted file mode 100644 index c013176..0000000 --- a/test/zones/qinq/bridge_vlanawarevnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces b/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces deleted file mode 100644 index 6b59164..0000000 --- a/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces +++ /dev/null @@ -1,31 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto sv_myzone -iface sv_myzone - vlan-raw-device eth0 - vlan-id 10 - vlan-protocol 802.1ad - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/bridge_vlanprotocol/interfaces b/test/zones/qinq/bridge_vlanprotocol/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/qinq/bridge_vlanprotocol/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/qinq/bridge_vlanprotocol/sdn_config b/test/zones/qinq/bridge_vlanprotocol/sdn_config deleted file mode 100644 index 20a8a51..0000000 --- a/test/zones/qinq/bridge_vlanprotocol/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/ovs/expected_sdn_interfaces b/test/zones/qinq/ovs/expected_sdn_interfaces deleted file mode 100644 index d25b2a8..0000000 --- a/test/zones/qinq/ovs/expected_sdn_interfaces +++ /dev/null @@ -1,71 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto ln_myzone2 -iface ln_myzone2 - link-type veth - veth-peer-name pr_myzone2 - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto myvnet2 -iface myvnet2 - bridge_ports z_myzone.101 - bridge_stp off - bridge_fd 0 - -auto myvnet3 -iface myvnet3 - bridge_ports z_myzone2.100 - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto pr_myzone2 -iface pr_myzone2 - link-type veth - veth-peer-name ln_myzone2 - -auto sv_myzone -iface sv_myzone - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q - -auto sv_myzone2 -iface sv_myzone2 - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options vlan_mode=dot1q-tunnel tag=20 other_config:qinq-ethtype=802.1q - -auto vmbr0 -iface vmbr0 - ovs_ports sv_myzone sv_myzone2 - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto z_myzone2 -iface z_myzone2 - bridge-stp off - bridge-ports sv_myzone2 ln_myzone2 - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/ovs/interfaces b/test/zones/qinq/ovs/interfaces deleted file mode 100644 index 14d2f1e..0000000 --- a/test/zones/qinq/ovs/interfaces +++ /dev/null @@ -1,9 +0,0 @@ -auto eth0 -iface eth0 inet manual - ovs_type OVSPort - ovs_bridge vmbr0 - -auto vmbr0 -iface vmbr0 inet manual - ovs_type OVSBridge - ovs_ports eth0 diff --git a/test/zones/qinq/ovs/sdn_config b/test/zones/qinq/ovs/sdn_config deleted file mode 100644 index 6321603..0000000 --- a/test/zones/qinq/ovs/sdn_config +++ /dev/null @@ -1,16 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - myvnet2 => { tag => 101, type => "vnet", zone => "myzone" }, - myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" }, - }, - }, - zones => { - ids => { - myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" }, - myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" }, - }, - }, -} diff --git a/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces b/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces deleted file mode 100644 index 5f47b28..0000000 --- a/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces +++ /dev/null @@ -1,37 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports pr_myzone - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto sv_myzone -iface sv_myzone - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q - -auto vmbr0 -iface vmbr0 - ovs_ports sv_myzone - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/ovs_notagvnet/interfaces b/test/zones/qinq/ovs_notagvnet/interfaces deleted file mode 100644 index 14d2f1e..0000000 --- a/test/zones/qinq/ovs_notagvnet/interfaces +++ /dev/null @@ -1,9 +0,0 @@ -auto eth0 -iface eth0 inet manual - ovs_type OVSPort - ovs_bridge vmbr0 - -auto vmbr0 -iface vmbr0 inet manual - ovs_type OVSBridge - ovs_ports eth0 diff --git a/test/zones/qinq/ovs_notagvnet/sdn_config b/test/zones/qinq/ovs_notagvnet/sdn_config deleted file mode 100644 index 2382f4d..0000000 --- a/test/zones/qinq/ovs_notagvnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces b/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces deleted file mode 100644 index d69d38c..0000000 --- a/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces +++ /dev/null @@ -1,37 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto sv_myzone -iface sv_myzone - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q - -auto vmbr0 -iface vmbr0 - ovs_ports sv_myzone - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/ovs_vlanawarevnet/interfaces b/test/zones/qinq/ovs_vlanawarevnet/interfaces deleted file mode 100644 index 14d2f1e..0000000 --- a/test/zones/qinq/ovs_vlanawarevnet/interfaces +++ /dev/null @@ -1,9 +0,0 @@ -auto eth0 -iface eth0 inet manual - ovs_type OVSPort - ovs_bridge vmbr0 - -auto vmbr0 -iface vmbr0 inet manual - ovs_type OVSBridge - ovs_ports eth0 diff --git a/test/zones/qinq/ovs_vlanawarevnet/sdn_config b/test/zones/qinq/ovs_vlanawarevnet/sdn_config deleted file mode 100644 index c013176..0000000 --- a/test/zones/qinq/ovs_vlanawarevnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces b/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces deleted file mode 100644 index aeefec9..0000000 --- a/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces +++ /dev/null @@ -1,35 +0,0 @@ -#version:1 - -auto ln_myzone -iface ln_myzone - link-type veth - veth-peer-name pr_myzone - -auto myvnet -iface myvnet - bridge_ports z_myzone.100 - bridge_stp off - bridge_fd 0 - -auto pr_myzone -iface pr_myzone - link-type veth - veth-peer-name ln_myzone - -auto sv_myzone -iface sv_myzone - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1ad - -auto vmbr0 -iface vmbr0 - ovs_ports sv_myzone - -auto z_myzone -iface z_myzone - bridge-stp off - bridge-ports sv_myzone ln_myzone - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/qinq/ovs_vlanprotocol/interfaces b/test/zones/qinq/ovs_vlanprotocol/interfaces deleted file mode 100644 index 14d2f1e..0000000 --- a/test/zones/qinq/ovs_vlanprotocol/interfaces +++ /dev/null @@ -1,9 +0,0 @@ -auto eth0 -iface eth0 inet manual - ovs_type OVSPort - ovs_bridge vmbr0 - -auto vmbr0 -iface vmbr0 inet manual - ovs_type OVSBridge - ovs_ports eth0 diff --git a/test/zones/qinq/ovs_vlanprotocol/sdn_config b/test/zones/qinq/ovs_vlanprotocol/sdn_config deleted file mode 100644 index 20a8a51..0000000 --- a/test/zones/qinq/ovs_vlanprotocol/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } }, - }, -} diff --git a/test/zones/simple/basic/expected_sdn_interfaces b/test/zones/simple/basic/expected_sdn_interfaces deleted file mode 100644 index 1e0c2c7..0000000 --- a/test/zones/simple/basic/expected_sdn_interfaces +++ /dev/null @@ -1,7 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - bridge_ports none - bridge_stp off - bridge_fd 0 diff --git a/test/zones/simple/basic/interfaces b/test/zones/simple/basic/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/simple/basic/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/simple/basic/sdn_config b/test/zones/simple/basic/sdn_config deleted file mode 100644 index 527dcba..0000000 --- a/test/zones/simple/basic/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, -} diff --git a/test/zones/simple/hetzner/expected_sdn_interfaces b/test/zones/simple/hetzner/expected_sdn_interfaces deleted file mode 100644 index f47ac53..0000000 --- a/test/zones/simple/hetzner/expected_sdn_interfaces +++ /dev/null @@ -1,19 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 144.76.100.65/29 - bridge_ports none - bridge_stp off - bridge_fd 0 - ip-forward on - -auto myvnet2 -iface myvnet2 - address 144.76.0.1/32 - up ip route add 144.76.200.65/32 dev myvnet2 - up ip route add 144.76.200.66/32 dev myvnet2 - bridge_ports none - bridge_stp off - bridge_fd 0 - ip-forward on diff --git a/test/zones/simple/hetzner/interfaces b/test/zones/simple/hetzner/interfaces deleted file mode 100644 index 5ab9635..0000000 --- a/test/zones/simple/hetzner/interfaces +++ /dev/null @@ -1,6 +0,0 @@ -auto eth0 -iface eth0 inet static - address 144.76.0.1 - netmask 255.255.255.255 - pointopoint 172.31.1.1 - gateway 172.31.1.1 \ No newline at end of file diff --git a/test/zones/simple/hetzner/sdn_config b/test/zones/simple/hetzner/sdn_config deleted file mode 100644 index 30773ca..0000000 --- a/test/zones/simple/hetzner/sdn_config +++ /dev/null @@ -1,34 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - myvnet2 => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, - - subnets => { - ids => { - 'myzone-144.76.100.64-29' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '144.76.100.65', - }, - 'myzone-144.76.200.65-32' => { - 'type' => 'subnet', - 'vnet' => 'myvnet2', - 'gateway' => '144.76.0.1', - }, - 'myzone-144.76.200.66-32' => { - 'type' => 'subnet', - 'vnet' => 'myvnet2', - 'gateway' => '144.76.0.1', - }, - } - } -} - - diff --git a/test/zones/simple/ipv4/expected_sdn_interfaces b/test/zones/simple/ipv4/expected_sdn_interfaces deleted file mode 100644 index 06e43ad..0000000 --- a/test/zones/simple/ipv4/expected_sdn_interfaces +++ /dev/null @@ -1,9 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 192.168.0.1/24 - bridge_ports none - bridge_stp off - bridge_fd 0 - ip-forward on diff --git a/test/zones/simple/ipv4/interfaces b/test/zones/simple/ipv4/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/simple/ipv4/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/simple/ipv4/sdn_config b/test/zones/simple/ipv4/sdn_config deleted file mode 100644 index dd77b75..0000000 --- a/test/zones/simple/ipv4/sdn_config +++ /dev/null @@ -1,22 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, - - subnets => { - ids => { 'myzone-192.168.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '192.168.0.1', - } - } - } -} - - diff --git a/test/zones/simple/ipv4snat/expected_sdn_interfaces b/test/zones/simple/ipv4snat/expected_sdn_interfaces deleted file mode 100644 index 69d7986..0000000 --- a/test/zones/simple/ipv4snat/expected_sdn_interfaces +++ /dev/null @@ -1,13 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 10.0.0.1/24 - post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 - post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 - bridge_ports none - bridge_stp off - bridge_fd 0 - ip-forward on diff --git a/test/zones/simple/ipv4snat/interfaces b/test/zones/simple/ipv4snat/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/simple/ipv4snat/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/simple/ipv4snat/sdn_config b/test/zones/simple/ipv4snat/sdn_config deleted file mode 100644 index 5936d7d..0000000 --- a/test/zones/simple/ipv4snat/sdn_config +++ /dev/null @@ -1,23 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, - - subnets => { - ids => { 'myzone-10.0.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '10.0.0.1', - 'snat' => 1 - } - } - } -} - - diff --git a/test/zones/simple/ipv4v6/expected_sdn_interfaces b/test/zones/simple/ipv4v6/expected_sdn_interfaces deleted file mode 100644 index 34ed5db..0000000 --- a/test/zones/simple/ipv4v6/expected_sdn_interfaces +++ /dev/null @@ -1,11 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 192.168.0.1/24 - address 2a08:2142:302:3::1/64 - bridge_ports none - bridge_stp off - bridge_fd 0 - ip-forward on - ip6-forward on diff --git a/test/zones/simple/ipv4v6/interfaces b/test/zones/simple/ipv4v6/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/simple/ipv4v6/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/simple/ipv4v6/sdn_config b/test/zones/simple/ipv4v6/sdn_config deleted file mode 100644 index b8ed848..0000000 --- a/test/zones/simple/ipv4v6/sdn_config +++ /dev/null @@ -1,27 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, - subnets => { - ids => { - 'myzone-192.168.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '192.168.0.1', - }, - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '2a08:2142:302:3::1', - } - } - } -} - - diff --git a/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces b/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces deleted file mode 100644 index 1e0c2c7..0000000 --- a/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces +++ /dev/null @@ -1,7 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - bridge_ports none - bridge_stp off - bridge_fd 0 diff --git a/test/zones/simple/ipv4v6nogateway/interfaces b/test/zones/simple/ipv4v6nogateway/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/simple/ipv4v6nogateway/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/simple/ipv4v6nogateway/sdn_config b/test/zones/simple/ipv4v6nogateway/sdn_config deleted file mode 100644 index dbd75c9..0000000 --- a/test/zones/simple/ipv4v6nogateway/sdn_config +++ /dev/null @@ -1,25 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, - subnets => { - ids => { - 'myzone-192.168.0.0-24' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - }, - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - } - } - } -} - - diff --git a/test/zones/simple/ipv6snat/expected_sdn_interfaces b/test/zones/simple/ipv6snat/expected_sdn_interfaces deleted file mode 100644 index d3adc24..0000000 --- a/test/zones/simple/ipv6snat/expected_sdn_interfaces +++ /dev/null @@ -1,13 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - address 2a08:2142:302:3::1/64 - post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1 - post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1 - post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1 - bridge_ports none - bridge_stp off - bridge_fd 0 - ip6-forward on diff --git a/test/zones/simple/ipv6snat/interfaces b/test/zones/simple/ipv6snat/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/simple/ipv6snat/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/simple/ipv6snat/sdn_config b/test/zones/simple/ipv6snat/sdn_config deleted file mode 100644 index bc38527..0000000 --- a/test/zones/simple/ipv6snat/sdn_config +++ /dev/null @@ -1,24 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "simple" } }, - }, - - subnets => { - ids => { - 'myzone-2a08:2142:302:3::-64' => { - 'type' => 'subnet', - 'vnet' => 'myvnet', - 'gateway' => '2a08:2142:302:3::1', - 'snat' => 1 - } - } - } -} - - diff --git a/test/zones/vlan/bridge/expected_sdn_interfaces b/test/zones/vlan/bridge/expected_sdn_interfaces deleted file mode 100644 index f9e96d1..0000000 --- a/test/zones/vlan/bridge/expected_sdn_interfaces +++ /dev/null @@ -1,23 +0,0 @@ -#version:1 - -auto ln_myvnet -iface ln_myvnet - link-type veth - veth-peer-name pr_myvnet - -auto myvnet -iface myvnet - bridge_ports ln_myvnet - bridge_stp off - bridge_fd 0 - -auto pr_myvnet -iface pr_myvnet - link-type veth - veth-peer-name ln_myvnet - -auto vmbr0v100 -iface vmbr0v100 - bridge_ports eth0.100 pr_myvnet - bridge_stp off - bridge_fd 0 diff --git a/test/zones/vlan/bridge/interfaces b/test/zones/vlan/bridge/interfaces deleted file mode 100644 index 68b6a88..0000000 --- a/test/zones/vlan/bridge/interfaces +++ /dev/null @@ -1,5 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/vlan/bridge/sdn_config b/test/zones/vlan/bridge/sdn_config deleted file mode 100644 index c6cfaaa..0000000 --- a/test/zones/vlan/bridge/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, - }, -} diff --git a/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces b/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces deleted file mode 100644 index a318c7a..0000000 --- a/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces +++ /dev/null @@ -1,7 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - bridge_ports vmbr0.100 - bridge_stp off - bridge_fd 0 diff --git a/test/zones/vlan/bridge_vlanaware/interfaces b/test/zones/vlan/bridge_vlanaware/interfaces deleted file mode 100644 index cfdfafe..0000000 --- a/test/zones/vlan/bridge_vlanaware/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 - bridge-vids 2-4094 - bridge-vlan-aware 1 diff --git a/test/zones/vlan/bridge_vlanaware/sdn_config b/test/zones/vlan/bridge_vlanaware/sdn_config deleted file mode 100644 index c6cfaaa..0000000 --- a/test/zones/vlan/bridge_vlanaware/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, - }, -} diff --git a/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces b/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces deleted file mode 100644 index ebf9d2e..0000000 --- a/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces +++ /dev/null @@ -1,9 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - bridge_ports vmbr0.100 - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 diff --git a/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces b/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces deleted file mode 100644 index 64eec9e..0000000 --- a/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet manual - bridge-ports eth0 - bridge-stp off - bridge-fd 0 - bridge-vlan-aware yes - bridge-vids 2-4096 diff --git a/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config b/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config deleted file mode 100644 index 67068f9..0000000 --- a/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => "100", type => "vnet", vlanaware => 1, zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, - }, -} diff --git a/test/zones/vlan/ovs/expected_sdn_interfaces b/test/zones/vlan/ovs/expected_sdn_interfaces deleted file mode 100644 index 044559e..0000000 --- a/test/zones/vlan/ovs/expected_sdn_interfaces +++ /dev/null @@ -1,17 +0,0 @@ -#version:1 - -auto ln_myvnet -iface ln_myvnet - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options tag=100 - -auto myvnet -iface myvnet - bridge_ports ln_myvnet - bridge_stp off - bridge_fd 0 - -auto vmbr0 -iface vmbr0 - ovs_ports ln_myvnet diff --git a/test/zones/vlan/ovs/interfaces b/test/zones/vlan/ovs/interfaces deleted file mode 100644 index 14d2f1e..0000000 --- a/test/zones/vlan/ovs/interfaces +++ /dev/null @@ -1,9 +0,0 @@ -auto eth0 -iface eth0 inet manual - ovs_type OVSPort - ovs_bridge vmbr0 - -auto vmbr0 -iface vmbr0 inet manual - ovs_type OVSBridge - ovs_ports eth0 diff --git a/test/zones/vlan/ovs/sdn_config b/test/zones/vlan/ovs/sdn_config deleted file mode 100644 index c6cfaaa..0000000 --- a/test/zones/vlan/ovs/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, - }, -} diff --git a/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces b/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces deleted file mode 100644 index 7bb73b6..0000000 --- a/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces +++ /dev/null @@ -1,19 +0,0 @@ -#version:1 - -auto ln_myvnet -iface ln_myvnet - ovs_type OVSIntPort - ovs_bridge vmbr0 - ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=100 - -auto myvnet -iface myvnet - bridge_ports ln_myvnet - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - -auto vmbr0 -iface vmbr0 - ovs_ports ln_myvnet diff --git a/test/zones/vlan/ovs_vlanware_vnet/interfaces b/test/zones/vlan/ovs_vlanware_vnet/interfaces deleted file mode 100644 index 14d2f1e..0000000 --- a/test/zones/vlan/ovs_vlanware_vnet/interfaces +++ /dev/null @@ -1,9 +0,0 @@ -auto eth0 -iface eth0 inet manual - ovs_type OVSPort - ovs_bridge vmbr0 - -auto vmbr0 -iface vmbr0 inet manual - ovs_type OVSBridge - ovs_ports eth0 diff --git a/test/zones/vlan/ovs_vlanware_vnet/sdn_config b/test/zones/vlan/ovs_vlanware_vnet/sdn_config deleted file mode 100644 index 9cfdb52..0000000 --- a/test/zones/vlan/ovs_vlanware_vnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } }, - }, -} diff --git a/test/zones/vxlan/basic/expected_sdn_interfaces b/test/zones/vxlan/basic/expected_sdn_interfaces deleted file mode 100644 index 7b73c3e..0000000 --- a/test/zones/vxlan/basic/expected_sdn_interfaces +++ /dev/null @@ -1,15 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan_remoteip 192.168.0.2 - vxlan_remoteip 192.168.0.3 - mtu 1450 diff --git a/test/zones/vxlan/basic/interfaces b/test/zones/vxlan/basic/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/vxlan/basic/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/vxlan/basic/sdn_config b/test/zones/vxlan/basic/sdn_config deleted file mode 100644 index f929304..0000000 --- a/test/zones/vxlan/basic/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } }, - }, -} diff --git a/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces b/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces deleted file mode 100644 index 55cdf9c..0000000 --- a/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces +++ /dev/null @@ -1,17 +0,0 @@ -#version:1 - -auto myvnet -iface myvnet - bridge_ports vxlan_myvnet - bridge_stp off - bridge_fd 0 - bridge-vlan-aware yes - bridge-vids 2-4094 - mtu 1450 - -auto vxlan_myvnet -iface vxlan_myvnet - vxlan-id 100 - vxlan_remoteip 192.168.0.2 - vxlan_remoteip 192.168.0.3 - mtu 1450 diff --git a/test/zones/vxlan/vlanawarevnet/interfaces b/test/zones/vxlan/vlanawarevnet/interfaces deleted file mode 100644 index 66bb826..0000000 --- a/test/zones/vxlan/vlanawarevnet/interfaces +++ /dev/null @@ -1,7 +0,0 @@ -auto vmbr0 -iface vmbr0 inet static - address 192.168.0.1/24 - gateway 192.168.0.254 - bridge-ports eth0 - bridge-stp off - bridge-fd 0 diff --git a/test/zones/vxlan/vlanawarevnet/sdn_config b/test/zones/vxlan/vlanawarevnet/sdn_config deleted file mode 100644 index 23fb557..0000000 --- a/test/zones/vxlan/vlanawarevnet/sdn_config +++ /dev/null @@ -1,11 +0,0 @@ -{ - version => 1, - vnets => { - ids => { - myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" }, - }, - }, - zones => { - ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } }, - }, -}