diff options
Diffstat (limited to 'src')
45 files changed, 4949 insertions, 4288 deletions
diff --git a/src/PVE/API2/Network/SDN.pm b/src/PVE/API2/Network/SDN.pm index d216e48..b514803 100644 --- a/src/PVE/API2/Network/SDN.pm +++ b/src/PVE/API2/Network/SDN.pm @@ -20,27 +20,27 @@ use PVE::API2::Network::SDN::Dns; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Vnets", path => 'vnets', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Zones", path => 'zones', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Controllers", path => 'controllers', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Ipams", path => 'ipams', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Dns", path => 'dns', }); @@ -51,35 +51,36 @@ __PACKAGE__->register_method({ method => 'GET', description => "Directory index.", permissions => { - check => ['perm', '/sdn', [ 'SDN.Audit' ]], + check => ['perm', '/sdn', ['SDN.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ - { id => 'vnets' }, - { id => 'zones' }, - { id => 'controllers' }, - { id => 'ipams' }, - { id => 'dns' }, - ]; + my $res = [ + { id => 'vnets' }, + { id => 'zones' }, + { id => 'controllers' }, + { id => 'ipams' }, + { id => 'dns' }, + ]; - return $res; - }}); + return $res; + }, +}); my $create_reload_network_worker = sub { my ($nodename) = @_; @@ -87,26 +88,29 @@ my $create_reload_network_worker = sub { # FIXME: how to proxy to final node ? my $upid; print "$nodename: reloading network config\n"; - run_command(['pvesh', 'set', "/nodes/$nodename/network"], outfunc => sub { - my $line = shift; - if ($line =~ /["']?(UPID:[^\s"']+)["']?$/) { - $upid = $1; - } - }); + 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 ({ +__PACKAGE__->register_method({ name => 'reload', protected => 1, path => '', method => 'PUT', description => "Apply sdn controller changes && reload.", permissions => { - check => ['perm', '/sdn', ['SDN.Allocate']], + check => ['perm', '/sdn', ['SDN.Allocate']], }, parameters => { additionalProperties => 0, @@ -120,26 +124,26 @@ __PACKAGE__->register_method ({ my $rpcenv = PVE::RPCEnvironment::get(); my $authuser = $rpcenv->get_user(); - PVE::Network::SDN::commit_config(); + 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 $@; - } + 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. + # FIXME: use libpve-apiclient (like in cluster join) to create + # tasks and moitor the tasks. - return; + 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 index b776273..e6eb4cb 100644 --- a/src/PVE/API2/Network/SDN/Controllers.pm +++ b/src/PVE/API2/Network/SDN/Controllers.pm @@ -34,56 +34,58 @@ my $api_sdn_controllers_config = sub { return $scfg; }; -__PACKAGE__->register_method ({ +__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/<controller>'", - user => 'all', + description => + "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/controllers/<controller>'", + 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.", - }, - }, + 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 => { type => 'boolean', optional => 1 }, - }, - }, - links => [ { rel => 'child', href => "{controller}" } ], + type => 'array', + items => { + type => "object", + properties => { + controller => { type => 'string' }, + type => { type => 'string' }, + state => { type => 'string', optional => 1 }, + pending => { type => 'boolean', optional => 1 }, + }, + }, + links => [{ rel => 'child', href => "{controller}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); my $cfg = {}; - if($param->{pending}) { + 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'); @@ -94,54 +96,55 @@ __PACKAGE__->register_method ({ $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 @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 $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; - } + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => '{controller}', method => 'GET', description => "Read sdn controller configuration.", permissions => { - check => ['perm', '/sdn/controllers/{controller}', ['SDN.Allocate']], - }, + 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.", - }, - }, + 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 ($param) = @_; my $cfg = {}; - if($param->{pending}) { + 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'); @@ -152,146 +155,162 @@ __PACKAGE__->register_method ({ $cfg = PVE::Network::SDN::Controllers::config(); } - return &$api_sdn_controllers_config($cfg, $param->{controller}); - }}); + return &$api_sdn_controllers_config($cfg, $param->{controller}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new sdn controller object.", permissions => { - check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'controller'); + 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); + 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 { + sub { - my $controller_cfg = PVE::Network::SDN::Controllers::config(); + 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"; - } + 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); + $controller_cfg->{ids}->{$id} = $opts; + $plugin->on_update_hook($id, $controller_cfg); - PVE::Network::SDN::Controllers::write_config($controller_cfg); + PVE::Network::SDN::Controllers::write_config($controller_cfg); - }, "create sdn controller object failed"); + }, + "create sdn controller object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{controller}', method => 'PUT', description => "Update sdn controller object configuration.", permissions => { - check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'controller'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'controller'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); PVE::Network::SDN::lock_sdn_config( - sub { + sub { - my $controller_cfg = PVE::Network::SDN::Controllers::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); - PVE::SectionConfig::assert_if_modified($controller_cfg, $digest); + PVE::SectionConfig::assert_if_modified($controller_cfg, $digest); - my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id); + 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); + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - my $options = $plugin->private()->{options}->{$scfg->{type}}; - PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + my $options = $plugin->private()->{options}->{ $scfg->{type} }; + PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); + } - for my $k (keys %{$opts}) { - $scfg->{$k} = $opts->{$k}; - } + for my $k (keys %{$opts}) { + $scfg->{$k} = $opts->{$k}; + } - $plugin->on_update_hook($id, $controller_cfg); + $plugin->on_update_hook($id, $controller_cfg); - PVE::Network::SDN::Controllers::write_config($controller_cfg); + PVE::Network::SDN::Controllers::write_config($controller_cfg); + }, + "update sdn controller object failed", + ); - }, "update sdn controller object failed"); - - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{controller}', method => 'DELETE', description => "Delete sdn controller object configuration.", permissions => { - check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + 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, - }), - }, + 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 ($param) = @_; - my $id = extract_param($param, 'controller'); + my $id = extract_param($param, 'controller'); PVE::Network::SDN::lock_sdn_config( - sub { + sub { - my $cfg = PVE::Network::SDN::Controllers::config(); + my $cfg = PVE::Network::SDN::Controllers::config(); - my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id); + my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id); - my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); - my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $zone_cfg = PVE::Network::SDN::Zones::config(); - $plugin->on_delete_hook($id, $zone_cfg); + $plugin->on_delete_hook($id, $zone_cfg); - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Controllers::write_config($cfg); + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Controllers::write_config($cfg); - }, "delete sdn controller object failed"); + }, + "delete sdn controller object failed", + ); - - return undef; - }}); + return undef; + }, +}); 1; diff --git a/src/PVE/API2/Network/SDN/Dns.pm b/src/PVE/API2/Network/SDN/Dns.pm index 826d111..c82e354 100644 --- a/src/PVE/API2/Network/SDN/Dns.pm +++ b/src/PVE/API2/Network/SDN/Dns.pm @@ -31,219 +31,234 @@ my $api_sdn_dns_config = sub { return $scfg; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "SDN dns index.", permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/dns/<dns>'", - user => 'all', + description => + "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/dns/<dns>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - type => { - description => "Only list sdn dns of specific type", - type => 'string', - enum => $sdn_dns_type_enum, - optional => 1, - }, - }, + 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}" } ], + type => 'array', + items => { + type => "object", + properties => { + dns => { type => 'string' }, + type => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{dns}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + my $cfg = PVE::Network::SDN::Dns::config(); - 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 @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 $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; + } - my $plugin_config = $cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); - push @$res, $scfg; - } - - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => '{dns}', method => 'GET', description => "Read sdn dns configuration.", permissions => { - check => ['perm', '/sdn/dns/{dns}', ['SDN.Allocate']], - }, + check => ['perm', '/sdn/dns/{dns}', ['SDN.Allocate']], + }, parameters => { - additionalProperties => 0, - properties => { - dns => get_standard_option('pve-sdn-dns-id'), - }, + additionalProperties => 0, + properties => { + dns => get_standard_option('pve-sdn-dns-id'), + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = PVE::Network::SDN::Dns::config(); + my $cfg = PVE::Network::SDN::Dns::config(); - return &$api_sdn_dns_config($cfg, $param->{dns}); - }}); + return &$api_sdn_dns_config($cfg, $param->{dns}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new sdn dns object.", permissions => { - check => ['perm', '/sdn/dns', ['SDN.Allocate']], + check => ['perm', '/sdn/dns', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Dns::Plugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'dns'); + 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); + 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 { + sub { - my $dns_cfg = PVE::Network::SDN::Dns::config(); + 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"; - } + 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; + $dns_cfg->{ids}->{$id} = $opts; - my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($opts->{type}); - $plugin->on_update_hook($opts); + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($opts->{type}); + $plugin->on_update_hook($opts); - PVE::Network::SDN::Dns::write_config($dns_cfg); + PVE::Network::SDN::Dns::write_config($dns_cfg); - }, "create sdn dns object failed"); + }, + "create sdn dns object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{dns}', method => 'PUT', description => "Update sdn dns object configuration.", permissions => { - check => ['perm', '/sdn/dns', ['SDN.Allocate']], + check => ['perm', '/sdn/dns', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'dns'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'dns'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); PVE::Network::SDN::lock_sdn_config( - sub { + sub { - my $dns_cfg = PVE::Network::SDN::Dns::config(); + my $dns_cfg = PVE::Network::SDN::Dns::config(); - PVE::SectionConfig::assert_if_modified($dns_cfg, $digest); + PVE::SectionConfig::assert_if_modified($dns_cfg, $digest); - my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id); + 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); + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - my $options = $plugin->private()->{options}->{$scfg->{type}}; - PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + my $options = $plugin->private()->{options}->{ $scfg->{type} }; + PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); + } - foreach my $k (%$opts) { - $scfg->{$k} = $opts->{$k}; - } + foreach my $k (%$opts) { + $scfg->{$k} = $opts->{$k}; + } - $plugin->on_update_hook($scfg); + $plugin->on_update_hook($scfg); - PVE::Network::SDN::Dns::write_config($dns_cfg); + PVE::Network::SDN::Dns::write_config($dns_cfg); - }, "update sdn dns object failed"); + }, + "update sdn dns object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{dns}', method => 'DELETE', description => "Delete sdn dns object configuration.", permissions => { - check => ['perm', '/sdn/dns', ['SDN.Allocate']], + 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, - }), - }, + 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 ($param) = @_; - my $id = extract_param($param, 'dns'); + my $id = extract_param($param, 'dns'); PVE::Network::SDN::lock_sdn_config( - sub { + sub { - my $cfg = PVE::Network::SDN::Dns::config(); + my $cfg = PVE::Network::SDN::Dns::config(); - my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id); + my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id); - my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Dns::write_config($cfg); + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Dns::write_config($cfg); - }, "delete sdn dns object failed"); + }, + "delete sdn dns object failed", + ); - return undef; - }}); + return undef; + }, +}); 1; diff --git a/src/PVE/API2/Network/SDN/Ipams.pm b/src/PVE/API2/Network/SDN/Ipams.pm index 27ead02..e30d28f 100644 --- a/src/PVE/API2/Network/SDN/Ipams.pm +++ b/src/PVE/API2/Network/SDN/Ipams.pm @@ -36,302 +36,323 @@ my $api_sdn_ipams_config = sub { return $scfg; }; -__PACKAGE__->register_method ({ +__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/<ipam>'", - user => 'all', + description => + "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/ipams/<ipam>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - type => { - description => "Only list sdn ipams of specific type", - type => 'string', - enum => $sdn_ipams_type_enum, - optional => 1, - }, - }, + 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}" } ], + type => 'array', + items => { + type => "object", + properties => { + ipam => { type => 'string' }, + type => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{ipam}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + my $cfg = PVE::Network::SDN::Ipams::config(); - 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 @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 $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; + } - my $plugin_config = $cfg->{ids}->{$id}; - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - push @$res, $scfg; - } - - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => '{ipam}', method => 'GET', description => "Read sdn ipam configuration.", permissions => { - check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']], - }, + check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']], + }, parameters => { - additionalProperties => 0, - properties => { - ipam => get_standard_option('pve-sdn-ipam-id'), - }, + additionalProperties => 0, + properties => { + ipam => get_standard_option('pve-sdn-ipam-id'), + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = PVE::Network::SDN::Ipams::config(); + my $cfg = PVE::Network::SDN::Ipams::config(); - return &$api_sdn_ipams_config($cfg, $param->{ipam}); - }}); + return &$api_sdn_ipams_config($cfg, $param->{ipam}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new sdn ipam object.", permissions => { - check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'ipam'); + 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); + 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 { + sub { - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - my $controller_cfg = PVE::Network::SDN::Controllers::config(); + 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"; - } + 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; + $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); + 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); + PVE::Network::SDN::Ipams::write_config($ipam_cfg); - }, "create sdn ipam object failed"); + }, + "create sdn ipam object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{ipam}', method => 'PUT', description => "Update sdn ipam object configuration.", permissions => { - check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'ipam'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'ipam'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); PVE::Network::SDN::lock_sdn_config( - sub { + sub { - my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); - PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest); + PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest); - my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id); + 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); + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - my $options = $plugin->private()->{options}->{$scfg->{type}}; - PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + my $options = $plugin->private()->{options}->{ $scfg->{type} }; + PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); + } - foreach my $k (%$opts) { - $scfg->{$k} = $opts->{$k}; - } + foreach my $k (%$opts) { + $scfg->{$k} = $opts->{$k}; + } - $plugin->on_update_hook($scfg); + $plugin->on_update_hook($scfg); - PVE::Network::SDN::Ipams::write_config($ipam_cfg); + PVE::Network::SDN::Ipams::write_config($ipam_cfg); - }, "update sdn ipam object failed"); + }, + "update sdn ipam object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{ipam}', method => 'DELETE', description => "Delete sdn ipam object configuration.", permissions => { - check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + 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, - }), - }, + 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 ($param) = @_; - my $id = extract_param($param, 'ipam'); + my $id = extract_param($param, 'ipam'); PVE::Network::SDN::lock_sdn_config( - sub { + sub { - my $cfg = PVE::Network::SDN::Ipams::config(); + my $cfg = PVE::Network::SDN::Ipams::config(); - my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id); + my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id); - my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); - my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Ipams::write_config($cfg); + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Ipams::write_config($cfg); - }, "delete sdn zone object failed"); + }, + "delete sdn zone object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'ipamindex', path => '{ipam}/status', method => 'GET', description => 'List PVE IPAM Entries', protected => 1, permissions => { - description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => + "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - ipam => get_standard_option('pve-sdn-ipam-id', { - completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams, - }), - }, + additionalProperties => 0, + properties => { + ipam => get_standard_option( + 'pve-sdn-ipam-id', + { + completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams, + }, + ), + }, }, returns => { - type => 'array', + type => 'array', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'ipam'); - die "Currently only PVE IPAM is supported!" if $id ne 'pve'; + my $id = extract_param($param, 'ipam'); + die "Currently only PVE IPAM is supported!" if $id ne 'pve'; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + my $privs = ['SDN.Audit', 'SDN.Allocate']; - my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup('pve'); - my $ipam_db = $ipam_plugin->read_db(); + my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup('pve'); + my $ipam_db = $ipam_plugin->read_db(); - my $result = []; + my $result = []; - for my $zone_id (keys %{$ipam_db->{zones}}) { - my $zone_config = PVE::Network::SDN::Zones::get_zone($zone_id, 1); + for my $zone_id (keys %{ $ipam_db->{zones} }) { + my $zone_config = PVE::Network::SDN::Zones::get_zone($zone_id, 1); next if !$zone_config || $zone_config->{ipam} ne 'pve' || !$zone_config->{dhcp}; - my $zone = $ipam_db->{zones}->{$zone_id}; + my $zone = $ipam_db->{zones}->{$zone_id}; - my $vnets = PVE::Network::SDN::Zones::get_vnets($zone_id, 1); + my $vnets = PVE::Network::SDN::Zones::get_vnets($zone_id, 1); - for my $subnet_cidr (keys %{$zone->{subnets}}) { - my $subnet = $zone->{subnets}->{$subnet_cidr}; - my $ip = new NetAddr::IP($subnet_cidr) or die 'Found invalid CIDR in IPAM'; + for my $subnet_cidr (keys %{ $zone->{subnets} }) { + my $subnet = $zone->{subnets}->{$subnet_cidr}; + my $ip = new NetAddr::IP($subnet_cidr) or die 'Found invalid CIDR in IPAM'; - my $vnet = undef; - for my $vnet_id (keys %$vnets) { - eval { - my ($zone, $subnetid, $subnet_cfg, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip( - $vnet_id, - $ip->addr, - ); + my $vnet = undef; + for my $vnet_id (keys %$vnets) { + eval { + my ($zone, $subnetid, $subnet_cfg, $ip) = + PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip( + $vnet_id, $ip->addr, + ); - $vnet = $subnet_cfg->{vnet}; - }; + $vnet = $subnet_cfg->{vnet}; + }; - last if $vnet; - } + last if $vnet; + } - next if !$vnet || !$rpcenv->check_any($authuser, "/sdn/zones/$zone_id/$vnet", $privs, 1); + next + if !$vnet + || !$rpcenv->check_any($authuser, "/sdn/zones/$zone_id/$vnet", $privs, 1); - for my $ip (keys %{$subnet->{ips}}) { - my $entry = $subnet->{ips}->{$ip}; - $entry->{zone} = $zone_id; - $entry->{subnet} = $subnet_cidr; - $entry->{ip} = $ip; - $entry->{vnet} = $vnet; + for my $ip (keys %{ $subnet->{ips} }) { + my $entry = $subnet->{ips}->{$ip}; + $entry->{zone} = $zone_id; + $entry->{subnet} = $subnet_cidr; + $entry->{ip} = $ip; + $entry->{vnet} = $vnet; - push @$result, $entry; - } - } - } + push @$result, $entry; + } + } + } - return $result; + return $result; }, }); diff --git a/src/PVE/API2/Network/SDN/Ips.pm b/src/PVE/API2/Network/SDN/Ips.pm index 0003b2a..5ff05e7 100644 --- a/src/PVE/API2/Network/SDN/Ips.pm +++ b/src/PVE/API2/Network/SDN/Ips.pm @@ -13,130 +13,126 @@ use PVE::RESTHandler; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'ipdelete', path => '', method => 'DELETE', description => 'Delete IP Mappings in a VNet', protected => 1, permissions => { - check => ['perm', '/sdn/zones/{zone}/{vnet}', [ 'SDN.Allocate' ]], + check => ['perm', '/sdn/zones/{zone}/{vnet}', ['SDN.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - zone => get_standard_option('pve-sdn-zone-id'), - vnet => get_standard_option('pve-sdn-vnet-id'), - mac => get_standard_option('mac-addr'), - ip => { - type => 'string', - format => 'ip', - description => 'The IP address to delete', - }, - }, + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + vnet => get_standard_option('pve-sdn-vnet-id'), + mac => get_standard_option('mac-addr'), + ip => { + type => 'string', + format => 'ip', + description => 'The IP address to delete', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $vnet = extract_param($param, 'vnet'); - my $mac = extract_param($param, 'mac'); - my $ip = extract_param($param, 'ip'); + my $vnet = extract_param($param, 'vnet'); + my $mac = extract_param($param, 'mac'); + my $ip = extract_param($param, 'ip'); - eval { - PVE::Network::SDN::Vnets::del_ip($vnet, $ip, '', $mac); - }; - die "$@\n" if $@; + eval { PVE::Network::SDN::Vnets::del_ip($vnet, $ip, '', $mac); }; + die "$@\n" if $@; - return undef; + return undef; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'ipcreate', path => '', method => 'POST', description => 'Create IP Mapping in a VNet', protected => 1, permissions => { - check => ['perm', '/sdn/zones/{zone}/{vnet}', [ 'SDN.Allocate' ]], + check => ['perm', '/sdn/zones/{zone}/{vnet}', ['SDN.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - zone => get_standard_option('pve-sdn-zone-id'), - vnet => get_standard_option('pve-sdn-vnet-id'), - mac => get_standard_option('mac-addr'), - ip => { - type => 'string', - format => 'ip', - description => 'The IP address to associate with the given MAC address', - }, - }, + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + vnet => get_standard_option('pve-sdn-vnet-id'), + mac => get_standard_option('mac-addr'), + ip => { + type => 'string', + format => 'ip', + description => 'The IP address to associate with the given MAC address', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $vnet = extract_param($param, 'vnet'); - my $mac = extract_param($param, 'mac'); - my $ip = extract_param($param, 'ip'); + my $vnet = extract_param($param, 'vnet'); + my $mac = extract_param($param, 'mac'); + my $ip = extract_param($param, 'ip'); - PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, undef); + PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, undef); - return undef; + return undef; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'ipupdate', path => '', method => 'PUT', description => 'Update IP Mapping in a VNet', protected => 1, permissions => { - check => ['perm', '/sdn/zones/{zone}/{vnet}', [ 'SDN.Allocate' ]], + check => ['perm', '/sdn/zones/{zone}/{vnet}', ['SDN.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - zone => get_standard_option('pve-sdn-zone-id'), - vnet => get_standard_option('pve-sdn-vnet-id'), - vmid => get_standard_option('pve-vmid', { - optional => 1, - }), - mac => get_standard_option('mac-addr'), - ip => { - type => 'string', - format => 'ip', - description => 'The IP address to associate with the given MAC address', - }, - }, + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + vnet => get_standard_option('pve-sdn-vnet-id'), + vmid => get_standard_option('pve-vmid', { + optional => 1, + }), + mac => get_standard_option('mac-addr'), + ip => { + type => 'string', + format => 'ip', + description => 'The IP address to associate with the given MAC address', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $vnet = extract_param($param, 'vnet'); - my $mac = extract_param($param, 'mac'); - my $vmid = extract_param($param, 'vmid'); - my $ip = extract_param($param, 'ip'); + my $vnet = extract_param($param, 'vnet'); + my $mac = extract_param($param, 'mac'); + my $vmid = extract_param($param, 'vmid'); + my $ip = extract_param($param, 'ip'); - my ($old_ip4, $old_ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnet, $mac); - my $old_ip = (Net::IP::ip_get_version($ip) == 4) ? $old_ip4 : $old_ip6; + my ($old_ip4, $old_ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnet, $mac); + my $old_ip = (Net::IP::ip_get_version($ip) == 4) ? $old_ip4 : $old_ip6; - PVE::Network::SDN::Vnets::del_ip($vnet, $old_ip, '', $mac); + PVE::Network::SDN::Vnets::del_ip($vnet, $old_ip, '', $mac); - eval { - PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, $vmid); - }; - my $error = $@; + eval { PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, $vmid); }; + my $error = $@; - if ($error) { - PVE::Network::SDN::Vnets::add_ip($vnet, $old_ip, '', $mac, $vmid); - } + if ($error) { + PVE::Network::SDN::Vnets::add_ip($vnet, $old_ip, '', $mac, $vmid); + } - die "$error\n" if $error; - return undef; + die "$error\n" if $error; + return undef; }, }); diff --git a/src/PVE/API2/Network/SDN/Subnets.pm b/src/PVE/API2/Network/SDN/Subnets.pm index 7a4c331..c9f5452 100644 --- a/src/PVE/API2/Network/SDN/Subnets.pm +++ b/src/PVE/API2/Network/SDN/Subnets.pm @@ -55,291 +55,309 @@ my $check_vnet_access = sub { $rpcenv->check_any($authuser, "/sdn/zones/$zoneid/$vnet", $privs); }; -__PACKAGE__->register_method ({ +__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/zones/<zone>/<vnet>'", - user => 'all', + description => + "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", + 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.", - }, + 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}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{subnet}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $vnetid = $param->{vnet}; - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - &$check_vnet_access($vnetid, $privs); + my $vnetid = $param->{vnet}; + my $privs = ['SDN.Audit', 'SDN.Allocate']; + &$check_vnet_access($vnetid, $privs); 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'); + 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}; + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{subnets}; } else { - $cfg = PVE::Network::SDN::Subnets::config(); + $cfg = PVE::Network::SDN::Subnets::config(); } - my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); - my $res = []; - foreach my $id (@sids) { - my $scfg = &$api_sdn_subnets_config($cfg, $id); - next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid; - push @$res, $scfg; - } + my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $scfg = &$api_sdn_subnets_config($cfg, $id); + next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid; + push @$res, $scfg; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => '{subnet}', method => 'GET', description => "Read sdn subnet configuration.", permissions => { - description => "Require 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => + "Require 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, 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.", - }, + 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 ($param) = @_; - my $vnet = extract_param($param, 'vnet'); - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - &$check_vnet_access($vnet, $privs); + my $vnet = extract_param($param, 'vnet'); + my $privs = ['SDN.Audit', 'SDN.Allocate']; + &$check_vnet_access($vnet, $privs); 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'); + 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}; + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{subnets}; } else { - $cfg = PVE::Network::SDN::Subnets::config(); + $cfg = PVE::Network::SDN::Subnets::config(); } my $scfg = &$api_sdn_subnets_config($cfg, $param->{subnet}); - raise_param_exc({ vnet => "wrong vnet"}) if $vnet ne $scfg->{vnet}; + raise_param_exc({ vnet => "wrong vnet" }) if $vnet ne $scfg->{vnet}; - return $scfg; - }}); + return $scfg; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new sdn subnet object.", permissions => { - description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => PVE::Network::SDN::SubnetPlugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $cidr = extract_param($param, 'subnet'); + my $type = extract_param($param, 'type'); + my $cidr = extract_param($param, 'subnet'); - my $vnet = $param->{vnet}; - my $privs = [ 'SDN.Allocate' ]; - &$check_vnet_access($vnet, $privs); + my $vnet = $param->{vnet}; + my $privs = ['SDN.Allocate']; + &$check_vnet_access($vnet, $privs); - # create /etc/pve/sdn directory - PVE::Cluster::check_cfs_quorum(); - mkdir("/etc/pve/sdn") if ! -d '/etc/pve/sdn'; + # 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 { + 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 $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 $scfg = undef; - if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) { - die "sdn subnet object ID '$id' already defined\n"; - } + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1); - $cfg->{ids}->{$id} = $opts; + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) { + die "sdn subnet object ID '$id' already defined\n"; + } - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); - PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet); + $cfg->{ids}->{$id} = $opts; - PVE::Network::SDN::Subnets::write_config($cfg); + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); + PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet); - }, "create sdn subnet object failed"); + PVE::Network::SDN::Subnets::write_config($cfg); - return undef; - }}); + }, + "create sdn subnet object failed", + ); -__PACKAGE__->register_method ({ + return undef; + }, +}); + +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{subnet}', method => 'PUT', description => "Update sdn subnet object configuration.", permissions => { - description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'subnet'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'subnet'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); - my $vnet = $param->{vnet}; + my $vnet = $param->{vnet}; - my $privs = [ 'SDN.Allocate' ]; - &$check_vnet_access($vnet, $privs); + my $privs = ['SDN.Allocate']; + &$check_vnet_access($vnet, $privs); PVE::Network::SDN::lock_sdn_config( - sub { + 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 $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; - my $zone = $zone_cfg->{ids}->{$zoneid}; + my $cfg = PVE::Network::SDN::Subnets::config(); + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; - my $scfg = &$api_sdn_subnets_config($cfg, $id); + my $scfg = &$api_sdn_subnets_config($cfg, $id); - PVE::SectionConfig::assert_if_modified($cfg, $digest); + PVE::SectionConfig::assert_if_modified($cfg, $digest); - my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1); + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1); - my $data = $cfg->{ids}->{$id}; - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - my $options = - PVE::Network::SDN::SubnetPlugin->private()->{options}->{$data->{type}}; - PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); - } - $data->{$_} = $opts->{$_} for keys $opts->%*; + my $data = $cfg->{ids}->{$id}; + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + my $options = + PVE::Network::SDN::SubnetPlugin->private()->{options}->{ $data->{type} }; + PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); + } + $data->{$_} = $opts->{$_} for keys $opts->%*; - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); - PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg); + 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); + PVE::Network::SDN::Subnets::write_config($cfg); - }, "update sdn subnet object failed"); + }, + "update sdn subnet object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{subnet}', method => 'DELETE', description => "Delete sdn subnet object configuration.", permissions => { - description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { + 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, - }), - }, + subnet => get_standard_option( + 'pve-sdn-subnet-id', + { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }, + ), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'subnet'); - my $vnet = extract_param($param, 'vnet'); - my $privs = [ 'SDN.Allocate' ]; - &$check_vnet_access($vnet, $privs); + my $id = extract_param($param, 'subnet'); + my $vnet = extract_param($param, 'vnet'); + my $privs = ['SDN.Allocate']; + &$check_vnet_access($vnet, $privs); 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); + sub { + my $cfg = PVE::Network::SDN::Subnets::config(); - my $vnets_cfg = PVE::Network::SDN::Vnets::config(); + my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1); - PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg); + my $vnets_cfg = PVE::Network::SDN::Vnets::config(); - my $zone_cfg = PVE::Network::SDN::Zones::config(); - my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone}; - my $zone = $zone_cfg->{ids}->{$zoneid}; + PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg); - PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg); + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; - delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg); - PVE::Network::SDN::Subnets::write_config($cfg); + delete $cfg->{ids}->{$id}; - }, "delete sdn subnet object failed"); + PVE::Network::SDN::Subnets::write_config($cfg); + }, + "delete sdn subnet object failed", + ); - return undef; - }}); + return undef; + }, +}); 1; diff --git a/src/PVE/API2/Network/SDN/Vnets.pm b/src/PVE/API2/Network/SDN/Vnets.pm index e48b048..5608283 100644 --- a/src/PVE/API2/Network/SDN/Vnets.pm +++ b/src/PVE/API2/Network/SDN/Vnets.pm @@ -25,17 +25,17 @@ use PVE::RESTHandler; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Firewall::Vnet", path => '{vnet}/firewall', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Subnets", path => '{vnet}/subnets', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Ips", path => '{vnet}/ips', }); @@ -46,7 +46,7 @@ my $api_sdn_vnets_config = sub { my $scfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id)); $scfg->{vnet} = $id; $scfg->{digest} = $cfg->{digest}; - + return $scfg; }; @@ -55,10 +55,11 @@ my $api_sdn_vnets_deleted_config = sub { 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; + 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; } }; @@ -73,273 +74,292 @@ my $check_vnet_access = sub { $rpcenv->check_any($authuser, "/sdn/zones/$zoneid/$vnet", $privs); }; -__PACKAGE__->register_method ({ +__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/zones/<zone>/<vnet>'", - user => 'all', + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'" + . " permissions on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { + additionalProperties => 0, + properties => { running => { type => 'boolean', optional => 1, description => "Display running config.", }, - pending => { - type => 'boolean', - optional => 1, - description => "Display pending config.", - }, - }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{vnet}" } ], + 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' ]; - my $scfg = &$api_sdn_vnets_config($cfg, $id); - my $zoneid = $scfg->{zone} // $scfg->{pending}->{zone}; - next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid/$id", $privs, 1); - - push @$res, $scfg; - } - - return $res; - }}); - -__PACKAGE__->register_method ({ + 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']; + my $scfg = &$api_sdn_vnets_config($cfg, $id); + my $zoneid = $scfg->{zone} // $scfg->{pending}->{zone}; + next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid/$id", $privs, 1); + + push @$res, $scfg; + } + + return $res; + }, +}); + +__PACKAGE__->register_method({ name => 'read', path => '{vnet}', method => 'GET', description => "Read sdn vnet configuration.", permissions => { - description => "Require 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => + "Require 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id', { - completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, - }), + 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.", - }, - }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; - - my $id = extract_param($param, 'vnet'); - - my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; - &$check_vnet_access($id, $privs); - - 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, $id); - }}); + my ($param) = @_; + + my $id = extract_param($param, 'vnet'); + + my $privs = ['SDN.Audit', 'SDN.Allocate']; + &$check_vnet_access($id, $privs); + + 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, $id); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new sdn vnet object.", permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], + check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::VnetPlugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'vnet'); + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'vnet'); - PVE::Cluster::check_cfs_quorum(); - mkdir("/etc/pve/sdn"); + 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); + 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; + 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); + 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::VnetPlugin->on_update_hook($id, $cfg); - PVE::Network::SDN::Vnets::write_config($cfg); + PVE::Network::SDN::Vnets::write_config($cfg); - }, "create sdn vnet object failed"); + }, + "create sdn vnet object failed", + ); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{vnet}', method => 'PUT', description => "Update sdn vnet object configuration.", permissions => { - description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => PVE::Network::SDN::VnetPlugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'vnet'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'vnet'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); - my $privs = [ 'SDN.Allocate' ]; - &$check_vnet_access($id, $privs); + my $privs = ['SDN.Allocate']; + &$check_vnet_access($id, $privs); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + } - PVE::Network::SDN::lock_sdn_config(sub { - my $cfg = PVE::Network::SDN::Vnets::config(); + PVE::Network::SDN::lock_sdn_config( + sub { + my $cfg = PVE::Network::SDN::Vnets::config(); - PVE::SectionConfig::assert_if_modified($cfg, $digest); + PVE::SectionConfig::assert_if_modified($cfg, $digest); - my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1); + my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1); - my $data = $cfg->{ids}->{$id}; - my $old_zone = $data->{zone}; + my $data = $cfg->{ids}->{$id}; + my $old_zone = $data->{zone}; - if ($delete) { - my $options = PVE::Network::SDN::VnetPlugin->private()->{options}->{$data->{type}}; - PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); - } + if ($delete) { + my $options = + PVE::Network::SDN::VnetPlugin->private()->{options}->{ $data->{type} }; + PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); + } - $data->{$_} = $opts->{$_} for keys $opts->%*; + $data->{$_} = $opts->{$_} for keys $opts->%*; - my $new_zone = $data->{zone}; - raise_param_exc({ zone => "cannot delete zone"}) if !$new_zone; - my $subnets = PVE::Network::SDN::Vnets::get_subnets($id); - raise_param_exc({ zone => "can't change zone if subnets exist"}) - if $subnets && $old_zone ne $new_zone; + my $new_zone = $data->{zone}; + raise_param_exc({ zone => "cannot delete zone" }) if !$new_zone; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($id); + raise_param_exc({ zone => "can't change zone if subnets exist" }) + if $subnets && $old_zone ne $new_zone; - 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); + 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::VnetPlugin->on_update_hook($id, $cfg); - PVE::Network::SDN::Vnets::write_config($cfg); + PVE::Network::SDN::Vnets::write_config($cfg); - }, "update sdn vnet object failed"); + }, + "update sdn vnet object failed", + ); - return undef; - } + return undef; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{vnet}', method => 'DELETE', description => "Delete sdn vnet object configuration.", permissions => { - description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", - user => 'all', + description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - vnet => get_standard_option('pve-sdn-vnet-id', { - completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, - }), - }, + 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'); + my ($param) = @_; - my $privs = [ 'SDN.Allocate' ]; - &$check_vnet_access($id, $privs); + 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(); + my $privs = ['SDN.Allocate']; + &$check_vnet_access($id, $privs); - PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg); + 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(); - delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Vnets::write_config($cfg); + PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg); - }, "delete sdn vnet object failed"); + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Vnets::write_config($cfg); + }, + "delete sdn vnet object failed", + ); - return undef; - } + return undef; + }, }); 1; diff --git a/src/PVE/API2/Network/SDN/Zones.pm b/src/PVE/API2/Network/SDN/Zones.pm index 2c27983..e53e6e7 100644 --- a/src/PVE/API2/Network/SDN/Zones.pm +++ b/src/PVE/API2/Network/SDN/Zones.pm @@ -39,330 +39,362 @@ my $api_sdn_zones_config = sub { $scfg->{digest} = $cfg->{digest}; if ($scfg->{nodes}) { - $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $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}); + $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}); + $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}); + $pending->{exitnodes} = + PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes}); } return $scfg; }; -__PACKAGE__->register_method ({ +__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/<zone>'", - user => 'all', + description => + "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>'", + 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.", - }, - }, + 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}, - dhcp => { type => 'string', optional => 1}, - pending => { type => 'boolean', optional => 1 }, - state => { type => 'string', optional => 1}, - nodes => { type => 'string', optional => 1}, - }, - }, - links => [ { rel => 'child', href => "{zone}" } ], + 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 }, + dhcp => { type => 'string', optional => 1 }, + pending => { type => 'boolean', optional => 1 }, + state => { type => 'string', optional => 1 }, + nodes => { type => 'string', optional => 1 }, + }, + }, + links => [{ rel => 'child', href => "{zone}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + 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'); + 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}; + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{zones}; } else { - $cfg = PVE::Network::SDN::Zones::config(); + $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 @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 $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; - } + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => '{zone}', method => 'GET', description => "Read sdn zone configuration.", permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], - }, + 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.", - } - }, + 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 ($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'); + 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}; + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{zones}; } else { - $cfg = PVE::Network::SDN::Zones::config(); + $cfg = PVE::Network::SDN::Zones::config(); } - return &$api_sdn_zones_config($cfg, $param->{zone}); - }}); + return &$api_sdn_zones_config($cfg, $param->{zone}); + }, +}); sub create_etc_interfaces_sdn_dir { mkdir("/etc/pve/sdn"); } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new sdn zone object.", permissions => { - check => ['perm', '/sdn/zones', ['SDN.Allocate']], + check => ['perm', '/sdn/zones', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Zones::Plugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $id = extract_param($param, 'zone'); + 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); + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); - PVE::Cluster::check_cfs_quorum(); - create_etc_interfaces_sdn_dir(); + PVE::Cluster::check_cfs_quorum(); + create_etc_interfaces_sdn_dir(); - 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::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 $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 $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 $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 $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}; + 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); + $zone_cfg->{ids}->{$id} = $opts; + $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); - PVE::Network::SDN::Zones::write_config($zone_cfg); + PVE::Network::SDN::Zones::write_config($zone_cfg); - }, "create sdn zone object failed"); + }, + "create sdn zone object failed", + ); - return; - }}); + return; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{zone}', method => 'PUT', description => "Update sdn zone object configuration.", permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], + check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], }, parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'zone'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'zone'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + } - 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::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); + PVE::SectionConfig::assert_if_modified($zone_cfg, $digest); - my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id); + 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); + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); - my $old_ipam = $scfg->{ipam}; + my $old_ipam = $scfg->{ipam}; - if ($delete) { - my $options = $plugin->private()->{options}->{$scfg->{type}}; - PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); - } + if ($delete) { + my $options = $plugin->private()->{options}->{ $scfg->{type} }; + PVE::SectionConfig::delete_from_config($scfg, $options, $opts, $delete); + } - $scfg->{$_} = $opts->{$_} for keys $opts->%*; + $scfg->{$_} = $opts->{$_} for keys $opts->%*; - my $new_ipam = $scfg->{ipam}; - if (!$new_ipam != !$old_ipam || (($new_ipam//'') ne ($old_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; - } - } + my $new_ipam = $scfg->{ipam}; + if (!$new_ipam != !$old_ipam || (($new_ipam // '') ne ($old_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; + } + } - my $dnsserver = $opts->{dns}; - raise_param_exc({ dns => "$dnsserver don't exist"}) if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; + 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 $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 $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}; + 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); + $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); - PVE::Network::SDN::Zones::write_config($zone_cfg); + PVE::Network::SDN::Zones::write_config($zone_cfg); - }, "update sdn zone object failed"); + }, + "update sdn zone object failed", + ); - return; - }}); + return; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{zone}', method => 'DELETE', description => "Delete sdn zone object configuration.", permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], + check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - zone => get_standard_option('pve-sdn-zone-id', { - completion => \&PVE::Network::SDN::Zones::complete_sdn_zones, - }), - }, + 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 ($param) = @_; - my $id = extract_param($param, 'zone'); + 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); + 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(); + 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); + $plugin->on_delete_hook($id, $vnet_cfg); - delete $cfg->{ids}->{$id}; + delete $cfg->{ids}->{$id}; - PVE::Network::SDN::Zones::write_config($cfg); - }, "delete sdn zone object failed"); + PVE::Network::SDN::Zones::write_config($cfg); + }, + "delete sdn zone object failed", + ); - return; - }}); + return; + }, +}); 1; diff --git a/src/PVE/API2/Network/SDN/Zones/Content.pm b/src/PVE/API2/Network/SDN/Zones/Content.pm index 211e71b..7666321 100644 --- a/src/PVE/API2/Network/SDN/Zones/Content.pm +++ b/src/PVE/API2/Network/SDN/Zones/Content.pm @@ -14,71 +14,75 @@ use PVE::Network::SDN; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "List zone content.", permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], + 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, - }), - }, + 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}" } ], + 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 ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $zoneid = $param->{zone}; + my $zoneid = $param->{zone}; - my $res = []; + 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; - } + 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; - }}); + return $res; + }, +}); 1; diff --git a/src/PVE/API2/Network/SDN/Zones/Status.pm b/src/PVE/API2/Network/SDN/Zones/Status.pm index 17de68f..4957567 100644 --- a/src/PVE/API2/Network/SDN/Zones/Status.pm +++ b/src/PVE/API2/Network/SDN/Zones/Status.pm @@ -16,96 +16,98 @@ use PVE::Exception qw(raise_param_exc); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network::SDN::Zones::Content", path => '{zone}/content', }); -__PACKAGE__->register_method ({ +__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', + 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') - }, + 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}" } ], + 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 ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $localnode = PVE::INotify::nodename(); + my $localnode = PVE::INotify::nodename(); - my $res = []; + my $res = []; - my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); + 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; - } + foreach my $id (sort keys %{$zone_status}) { + my $item->{zone} = $id; + $item->{status} = $zone_status->{$id}->{'status'}; + push @$res, $item; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'diridx', path => '{zone}', method => 'GET', description => "", permissions => { - check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], + 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'), - }, + 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}" } ], + type => 'array', + items => { + type => "object", + properties => { + subdir => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{subdir}" }], }, code => sub { - my ($param) = @_; - my $res = [ - { subdir => 'content' }, - ]; + my ($param) = @_; + my $res = [ + { subdir => 'content' }, + ]; - return $res; - }}); + return $res; + }, +}); 1; diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm index 68f9e0f..ef938c4 100644 --- a/src/PVE/Network/SDN.pm +++ b/src/PVE/Network/SDN.pm @@ -30,9 +30,7 @@ my $parse_running_cfg = sub { return $cfg if !defined($raw) || $raw eq ''; - eval { - $cfg = from_json($raw); - }; + eval { $cfg = from_json($raw); }; return {} if $@; return $cfg; @@ -48,30 +46,27 @@ my $write_running_cfg = sub { 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 $cmd = ['ifquery', '-a', '-c', '-o', 'json']; my $result = ''; my $reader = sub { $result .= shift }; - eval { - run_command($cmd, outfunc => $reader); - }; + 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}, - }; + my $name = $interface->{name}; + $interfaces->{$name} = { + status => $interface->{status}, + config => $interface->{config}, + config_status => $interface->{config_status}, + }; } return $interfaces; @@ -80,7 +75,7 @@ sub ifquery_check { sub status { my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status(); - return($zone_status, $vnet_status); + return ($zone_status, $vnet_status); } sub running_config { @@ -96,45 +91,50 @@ sub pending_config { 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"}); + 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}); - my $running_value = PVE::Network::SDN::encode_value(undef, $key, $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}; + 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}); + my $running_value = + PVE::Network::SDN::encode_value(undef, $key, $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 }; } @@ -144,9 +144,9 @@ sub commit_config { my $version = $cfg->{version}; if ($version) { - $version++; + $version++; } else { - $version = 1; + $version = 1; } my $vnets_cfg = PVE::Network::SDN::Vnets::config(); @@ -159,7 +159,13 @@ sub commit_config { my $controllers = { ids => $controllers_cfg->{ids} }; my $subnets = { ids => $subnets_cfg->{ids} }; - $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets }; + $cfg = { + version => $version, + vnets => $vnets, + zones => $zones, + controllers => $controllers, + subnets => $subnets, + }; cfs_write_file($running_cfg, $cfg); } @@ -192,21 +198,27 @@ sub get_local_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 $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid); + my $zoneid = $vnet->{zone}; + my $comments = $vnet->{alias}; - my $privs = [ 'SDN.Audit', 'SDN.Use' ]; + my $privs = ['SDN.Audit', 'SDN.Use']; - next if !$zoneid; - next if !$rpcenv->check_sdn_bridge($authuser, $zoneid, $vnetid, $privs, 1); + next if !$zoneid; + next if !$rpcenv->check_sdn_bridge($authuser, $zoneid, $vnetid, $privs, 1); - my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid); + 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 }; + 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; @@ -215,13 +227,14 @@ sub get_local_vnets { sub generate_zone_config { my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config(); if ($raw_config) { - eval { - my $net_cfg = PVE::INotify::read_file('interfaces', 1); - my $opts = $net_cfg->{data}->{options}; - log_warn("missing 'source /etc/network/interfaces.d/sdn' directive for SDN support!\n") - if ! grep { $_->[1] =~ m!^source /etc/network/interfaces.d/(:?sdn|\*)! } @$opts; - }; - log_warn("Failed to read network interfaces definition - $@") if $@; + eval { + my $net_cfg = PVE::INotify::read_file('interfaces', 1); + my $opts = $net_cfg->{data}->{options}; + log_warn( + "missing 'source /etc/network/interfaces.d/sdn' directive for SDN support!\n") + if !grep { $_->[1] =~ m!^source /etc/network/interfaces.d/(:?sdn|\*)! } @$opts; + }; + log_warn("Failed to read network interfaces definition - $@") if $@; } PVE::Network::SDN::Zones::write_etc_network_config($raw_config); } @@ -245,67 +258,66 @@ sub encode_value { my ($type, $key, $value) = @_; if ($key eq 'nodes' || $key eq 'exitnodes' || $key eq 'dhcp-range') { - if (ref($value) eq 'HASH') { - return join(',', sort keys(%$value)); - } elsif (ref($value) eq 'ARRAY') { - return join(',', sort @$value); - } else { - return $value; - } + if (ref($value) eq 'HASH') { + return join(',', sort keys(%$value)); + } elsif (ref($value) eq 'ARRAY') { + return join(',', sort @$value); + } else { + return $value; + } } return $value; } - #helpers sub api_request { my ($method, $url, $headers, $data, $expected_fingerprint) = @_; my $encoded_data = $data ? to_json($data) : undef; - my $req = HTTP::Request->new($method,$url, $headers, $encoded_data); + my $req = HTTP::Request->new($method, $url, $headers, $encoded_data); my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 30); my $datacenter_cfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); if (my $proxy = $datacenter_cfg->{http_proxy}) { - $ua->proxy(['http', 'https'], $proxy); + $ua->proxy(['http', 'https'], $proxy); } else { - $ua->env_proxy; + $ua->env_proxy; } if (defined($expected_fingerprint)) { - my $ssl_verify_callback = sub { - my (undef, undef, undef, undef, $cert, $depth) = @_; - - # we don't care about intermediate or root certificates, always return as valid as the - # callback will be executed for all levels and all must be valid. - return 1 if $depth != 0; - - my $fingerprint = Net::SSLeay::X509_get_fingerprint($cert, 'sha256'); - - return $fingerprint eq $expected_fingerprint ? 1 : 0; - }; - $ua->ssl_opts( - verify_hostname => 0, - SSL_verify_mode => SSL_VERIFY_PEER, - SSL_verify_callback => $ssl_verify_callback, - ); + my $ssl_verify_callback = sub { + my (undef, undef, undef, undef, $cert, $depth) = @_; + + # we don't care about intermediate or root certificates, always return as valid as the + # callback will be executed for all levels and all must be valid. + return 1 if $depth != 0; + + my $fingerprint = Net::SSLeay::X509_get_fingerprint($cert, 'sha256'); + + return $fingerprint eq $expected_fingerprint ? 1 : 0; + }; + $ua->ssl_opts( + verify_hostname => 0, + SSL_verify_mode => SSL_VERIFY_PEER, + SSL_verify_callback => $ssl_verify_callback, + ); } my $response = $ua->request($req); if (!$response->is_success) { - my $msg = $response->message || 'unknown'; - my $code = $response->code; - die "Invalid response from server: $code $msg\n"; + my $msg = $response->message || 'unknown'; + my $code = $response->code; + die "Invalid response from server: $code $msg\n"; } my $raw = ''; if (defined($response->decoded_content)) { - $raw = $response->decoded_content; + $raw = $response->decoded_content; } else { - $raw = $response->content; + $raw = $response->content; } return if $raw eq ''; diff --git a/src/PVE/Network/SDN/Controllers.pm b/src/PVE/Network/SDN/Controllers.pm index 9e8f3aa..6024f22 100644 --- a/src/PVE/Network/SDN/Controllers.pm +++ b/src/PVE/Network/SDN/Controllers.pm @@ -22,7 +22,6 @@ PVE::Network::SDN::Controllers::IsisPlugin->register(); PVE::Network::SDN::Controllers::FaucetPlugin->register(); PVE::Network::SDN::Controllers::Plugin->init(); - sub sdn_controllers_config { my ($cfg, $id, $noerr) = @_; @@ -36,7 +35,7 @@ sub sdn_controllers_config { sub config { my $config = cfs_read_file("sdn/controllers.cfg"); - $config = cfs_read_file("sdn/controllers.cfg") if !keys %{$config->{ids}}; + $config = cfs_read_file("sdn/controllers.cfg") if !keys %{ $config->{ids} }; return $config; } @@ -58,7 +57,7 @@ sub lock_sdn_controllers_config { sub sdn_controllers_ids { my ($cfg) = @_; - return sort keys %{$cfg->{ids}}; + return sort keys %{ $cfg->{ids} }; } sub complete_sdn_controller { @@ -66,13 +65,14 @@ sub complete_sdn_controller { my $cfg = PVE::Network::SDN::running_config(); - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_controllers_ids($cfg) ]; + return $cmdname eq 'add' ? [] : [PVE::Network::SDN::sdn_controllers_ids($cfg)]; } sub read_etc_network_interfaces { # read main config for physical interfaces my $current_config_file = "/etc/network/interfaces"; - my $fh = IO::File->new($current_config_file) or die "failed to open $current_config_file - $!\n"; + my $fh = IO::File->new($current_config_file) + or die "failed to open $current_config_file - $!\n"; my $interfaces_config = PVE::INotify::read_etc_network_interfaces($current_config_file, $fh); $fh->close(); @@ -92,54 +92,62 @@ sub generate_controller_config { # 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; - } + 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 %{ $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 %{ $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); - } + 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(); @@ -147,10 +155,10 @@ sub reload_controller { 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(); + 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(); } } @@ -162,10 +170,10 @@ sub generate_controller_rawconfig { 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); + 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; } @@ -177,10 +185,10 @@ sub write_controller_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); + 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); } } diff --git a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm index 53963e5..dd835e4 100644 --- a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm @@ -19,36 +19,36 @@ sub type { 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' - }, + '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 }, + '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 }, }; } @@ -67,66 +67,69 @@ sub generate_controller_config { 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 ($ifaceip, $interface) = + PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); my $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); my $remoteas = $ebgp ? "external" : $asn; #global options my @controller_config = ( - "bgp router-id $routerid", - "no bgp default ipv4-unicast", - "coalesce-time 1000" + "bgp router-id $routerid", "no bgp default ipv4-unicast", "coalesce-time 1000", ); - push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0; + push(@{ $bgp->{""} }, @controller_config) if keys %{$bgp} == 0; @controller_config = (); - if($ebgp) { - push @controller_config, "bgp disable-ebgp-connected-route-check" if $loopback; + 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; + 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 @controller_config, "neighbor $address peer-group BGP"; } - push(@{$bgp->{""}}, @controller_config); + 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"); + 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}}, "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); + $config->{frr_prefix_list}->{loopbacks_ips}->{10} = "permit 0.0.0.0/0 le 32"; + push(@{ $config->{frr_ip_protocol} }, "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; @@ -141,10 +144,10 @@ 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); + 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); } } @@ -154,7 +157,7 @@ sub on_update_hook { # 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}}) { + foreach my $id (keys %{ $controller_cfg->{ids} }) { next if $id eq $controllerid; my $controller = $controller_cfg->{ids}->{$id}; next if $controller->{type} ne "bgp"; @@ -181,4 +184,3 @@ sub reload_controller { 1; - diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm index 5b1a9aa..7211510 100644 --- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm @@ -20,23 +20,24 @@ sub type { sub properties { return { - asn => { - type => 'integer', - description => "autonomous system number", - minimum => 0, - maximum => 4294967296 - }, - peers => { - description => "peers address list.", - type => 'string', format => 'ip-list' - }, + 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 }, + 'asn' => { optional => 0 }, + 'peers' => { optional => 0 }, }; } @@ -57,33 +58,34 @@ sub generate_controller_config { my $isisrouter = find_isis_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; + $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; } elsif ($isisrouter) { - $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; + $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; } 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 ($ifaceip, $interface) = + PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); my $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); my $remoteas = $ebgp ? "external" : $asn; #global options my @controller_config = ( - "bgp router-id $routerid", - "no bgp hard-administrative-reset", - "no bgp default ipv4-unicast", - "coalesce-time 1000", - "no bgp graceful-restart notification", + "bgp router-id $routerid", + "no bgp hard-administrative-reset", + "no bgp default ipv4-unicast", + "coalesce-time 1000", + "no bgp graceful-restart notification", ); - push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0; + push(@{ $bgp->{""} }, @controller_config) if keys %{$bgp} == 0; @controller_config = (); @@ -97,11 +99,11 @@ sub generate_controller_config { # VTEP peers foreach my $address (@peers) { - next if $address eq $ifaceip; - push @controller_config, "neighbor $address peer-group VTEP"; + next if $address eq $ifaceip; + push @controller_config, "neighbor $address peer-group VTEP"; } - push(@{$bgp->{""}}, @controller_config); + push(@{ $bgp->{""} }, @controller_config); # address-family l2vpn @controller_config = (); @@ -110,11 +112,11 @@ sub generate_controller_config { push @controller_config, "neighbor VTEP route-map MAP_VTEP_OUT out"; push @controller_config, "advertise-all-vni"; push @controller_config, "autort as $autortas" if $autortas; - push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config); + 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 ); + push(@{ $config->{frr_routemap}->{'MAP_VTEP_IN'} }, $routemap); + push(@{ $config->{frr_routemap}->{'MAP_VTEP_OUT'} }, $routemap); return $config; } @@ -131,7 +133,8 @@ sub generate_controller_zone_config { 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'}; + $rt_import = [PVE::Tools::split_list($plugin_config->{'rt-import'})] + if $plugin_config->{'rt-import'}; my $asn = $controller->{asn}; my @peers; @@ -142,18 +145,19 @@ sub generate_controller_zone_config { my $bgprouter = find_bgp_controller($local_node, $controller_cfg); my $isisrouter = find_isis_controller($local_node, $controller_cfg); - if($bgprouter) { + 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; + $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + $asn = $bgprouter->{asn} if $bgprouter->{asn}; + $autortas = $controller->{'asn'} if $ebgp; } elsif ($isisrouter) { $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; } return if !$vrf || !$vrfvxlan || !$asn; - my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + my ($ifaceip, $interface) = + PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); my $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); my $is_gateway = $exitnodes->{$local_node}; @@ -164,30 +168,31 @@ sub generate_controller_zone_config { #avoid to routes between nodes through the exit nodes #null routes subnets of other zones if ($is_gateway) { - my $subnets = PVE::Network::SDN::Vnets::get_subnets(); - my $cidrs = {}; - foreach my $subnetid (sort keys %{$subnets}) { - my $subnet = $subnets->{$subnetid}; - my $cidr = $subnet->{cidr}; - my $zone = $subnet->{zone}; - my ($ip, $mask) = split(/\//, $cidr); - $cidrs->{$ip} = $mask if $zone ne $id; - - } - - my @sorted_ip = - map { $_->[0] } - sort { $a->[1] <=> $b->[1] } - map { [ $_, eval { Net::IP->new( $_ )->intip } ] } - keys %{$cidrs} if $cidrs; - - foreach my $ip (@sorted_ip) { - my $ipversion = Net::IP::ip_is_ipv4($ip) ? 'ip' : 'ipv6'; - push @controller_config, "$ipversion route $ip/$cidrs->{$ip} null0"; - } + my $subnets = PVE::Network::SDN::Vnets::get_subnets(); + my $cidrs = {}; + foreach my $subnetid (sort keys %{$subnets}) { + my $subnet = $subnets->{$subnetid}; + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + my ($ip, $mask) = split(/\//, $cidr); + $cidrs->{$ip} = $mask if $zone ne $id; + + } + + my @sorted_ip = + map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [$_, eval { Net::IP->new($_)->intip }] } + keys %{$cidrs} + if $cidrs; + + foreach my $ip (@sorted_ip) { + my $ipversion = Net::IP::ip_is_ipv4($ip) ? 'ip' : 'ipv6'; + push @controller_config, "$ipversion route $ip/$cidrs->{$ip} null0"; + } } - push(@{$config->{frr}->{vrf}->{"$vrf"}}, @controller_config); + push(@{ $config->{frr}->{vrf}->{"$vrf"} }, @controller_config); #main vrf router @controller_config = (); @@ -195,85 +200,149 @@ sub generate_controller_zone_config { push @controller_config, "no bgp hard-administrative-reset"; push @controller_config, "no bgp graceful-restart notification"; -# push @controller_config, "!"; - push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config); + # 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"); + 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", + ); } if ($is_gateway) { - $config->{frr_prefix_list}->{'only_default'}->{1} = "permit 0.0.0.0/0"; - $config->{frr_prefix_list_v6}->{'only_default_v6'}->{1} = "permit ::/0"; - - if (!$exitnodes_primary || $exitnodes_primary eq $local_node) { - #filter default route coming from other exit nodes on primary node or both nodes if no primary is defined. - my $routemap_config_v6 = (); - push @{$routemap_config_v6}, "match ipv6 address prefix-list only_default_v6"; - my $routemap_v6 = { rule => $routemap_config_v6, action => "deny" }; - unshift(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap_v6); - - my $routemap_config = (); - push @{$routemap_config}, "match ip address prefix-list only_default"; - my $routemap = { rule => $routemap_config, action => "deny" }; - unshift(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap); - - } elsif ($exitnodes_primary ne $local_node) { - my $routemap_config_v6 = (); - push @{$routemap_config_v6}, "match ipv6 address prefix-list only_default_v6"; - push @{$routemap_config_v6}, "set metric 200"; - my $routemap_v6 = { rule => $routemap_config_v6, action => "permit" }; - unshift(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap_v6); - - my $routemap_config = (); - push @{$routemap_config}, "match ip address prefix-list only_default"; - 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); + $config->{frr_prefix_list}->{'only_default'}->{1} = "permit 0.0.0.0/0"; + $config->{frr_prefix_list_v6}->{'only_default_v6'}->{1} = "permit ::/0"; + + if (!$exitnodes_primary || $exitnodes_primary eq $local_node) { + #filter default route coming from other exit nodes on primary node or both nodes if no primary is defined. + my $routemap_config_v6 = (); + push @{$routemap_config_v6}, "match ipv6 address prefix-list only_default_v6"; + my $routemap_v6 = { rule => $routemap_config_v6, action => "deny" }; + unshift(@{ $config->{frr_routemap}->{'MAP_VTEP_IN'} }, $routemap_v6); + + my $routemap_config = (); + push @{$routemap_config}, "match ip address prefix-list only_default"; + my $routemap = { rule => $routemap_config, action => "deny" }; + unshift(@{ $config->{frr_routemap}->{'MAP_VTEP_IN'} }, $routemap); + + } elsif ($exitnodes_primary ne $local_node) { + my $routemap_config_v6 = (); + push @{$routemap_config_v6}, "match ipv6 address prefix-list only_default_v6"; + push @{$routemap_config_v6}, "set metric 200"; + my $routemap_v6 = { rule => $routemap_config_v6, action => "permit" }; + unshift(@{ $config->{frr_routemap}->{'MAP_VTEP_OUT'} }, $routemap_v6); + + my $routemap_config = (); + push @{$routemap_config}, "match ip address prefix-list only_default"; + 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); + @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); + @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; @@ -296,20 +365,20 @@ sub generate_controller_vnet_config { 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"; + my $cidr = $subnet->{cidr}; + push @controller_config, "ip route $cidr 10.255.255.2 xvrf_$zoneid"; } - push(@{$config->{frr_ip_protocol}}, @controller_config); + push(@{ $config->{frr_ip_protocol} }, @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); + 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); } } @@ -319,12 +388,12 @@ sub on_update_hook { # 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; + 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; } } @@ -332,12 +401,12 @@ sub find_bgp_controller { my ($nodename, $controller_cfg) = @_; my $res = undef; - foreach my $id (keys %{$controller_cfg->{ids}}) { - my $controller = $controller_cfg->{ids}->{$id}; - next if $controller->{type} ne 'bgp'; - next if $controller->{node} ne $nodename; - $res = $controller; - last; + foreach my $id (keys %{ $controller_cfg->{ids} }) { + my $controller = $controller_cfg->{ids}->{$id}; + next if $controller->{type} ne 'bgp'; + next if $controller->{node} ne $nodename; + $res = $controller; + last; } return $res; } @@ -346,128 +415,130 @@ sub find_isis_controller { my ($nodename, $controller_cfg) = @_; my $res = undef; - foreach my $id (keys %{$controller_cfg->{ids}}) { - my $controller = $controller_cfg->{ids}->{$id}; - next if $controller->{type} ne 'isis'; - next if $controller->{node} ne $nodename; - $res = $controller; - last; + foreach my $id (keys %{ $controller_cfg->{ids} }) { + my $controller = $controller_cfg->{ids}->{$id}; + next if $controller->{type} ne 'isis'; + next if $controller->{node} ne $nodename; + $res = $controller; + last; } return $res; } -sub generate_frr_recurse{ - my ($final_config, $content, $parentkey, $level) = @_; - - my $keylist = {}; - $keylist->{'address-family'} = 1; - $keylist->{router} = 1; - - my $exitkeylist = {}; - $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 keys %$content) { - next if $key eq 'vrf'; - 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}); - } +sub generate_frr_recurse { + my ($final_config, $content, $parentkey, $level) = @_; + + my $keylist = {}; + $keylist->{'address-family'} = 1; + $keylist->{router} = 1; + + my $exitkeylist = {}; + $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 keys %$content) { + next if $key eq 'vrf'; + 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; + push @{$final_config}, map { $padding . "$_" } @$content; } } sub generate_frr_vrf { - my ($final_config, $vrfs) = @_; + my ($final_config, $vrfs) = @_; - return if !$vrfs; + return if !$vrfs; - my @config = (); + my @config = (); - foreach my $id (sort keys %$vrfs) { - my $vrf = $vrfs->{$id}; - push @config, "!"; - push @config, "vrf $id"; - foreach my $rule (@$vrf) { - push @config, " $rule"; + foreach my $id (sort keys %$vrfs) { + my $vrf = $vrfs->{$id}; + push @config, "!"; + push @config, "vrf $id"; + foreach my $rule (@$vrf) { + push @config, " $rule"; - } - push @config, "exit-vrf"; + } + push @config, "exit-vrf"; } push @{$final_config}, @config; } sub generate_frr_simple_list { - my ($final_config, $rules) = @_; + my ($final_config, $rules) = @_; - return if !$rules; + return if !$rules; - my @config = (); - push @{$final_config}, "!"; - foreach my $rule (sort @$rules) { - push @{$final_config}, $rule; - } + my @config = (); + push @{$final_config}, "!"; + foreach my $rule (sort @$rules) { + push @{$final_config}, $rule; + } } sub generate_frr_interfaces { - my ($final_config, $interfaces) = @_; - - foreach my $k (sort keys %$interfaces) { - my $iface = $interfaces->{$k}; - push @{$final_config}, "!"; - push @{$final_config}, "interface $k"; - foreach my $rule (sort @$iface) { - push @{$final_config}, " $rule"; - } - } + my ($final_config, $interfaces) = @_; + + foreach my $k (sort keys %$interfaces) { + my $iface = $interfaces->{$k}; + push @{$final_config}, "!"; + push @{$final_config}, "interface $k"; + foreach my $rule (sort @$iface) { + push @{$final_config}, " $rule"; + } + } } 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"; - } - } + 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 { @@ -476,24 +547,24 @@ sub generate_frr_list { my $config = []; for my $id (sort keys %$lists) { - my $list = $lists->{$id}; + my $list = $lists->{$id}; - for my $seq (sort keys %$list) { - my $rule = $list->{$seq}; - push @$config, "$type $id seq $seq $rule"; - } + for my $seq (sort keys %$list) { + my $rule = $list->{$seq}; + push @$config, "$type $id seq $seq $rule"; + } } if (@$config > 0) { - push @{$final_config}, "!", @$config; + push @{$final_config}, "!", @$config; } } sub read_local_frr_config { if (-e "/etc/frr/frr.conf.local") { - return file_get_contents("/etc/frr/frr.conf.local"); + return file_get_contents("/etc/frr/frr.conf.local"); } -}; +} sub generate_controller_rawconfig { my ($class, $plugin_config, $config) = @_; @@ -510,7 +581,7 @@ sub generate_controller_rawconfig { my $local_conf = read_local_frr_config(); if ($local_conf) { - parse_merge_frr_local_config($config, $local_conf); + parse_merge_frr_local_config($config, $local_conf); } generate_frr_vrf($final_config, $config->{frr}->{vrf}); @@ -544,63 +615,63 @@ sub parse_merge_frr_local_config { 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/^interface (.+)$/) { - $section = \$config->{'frr_interfaces'}->{$1}; - next; - } elsif ($line =~ m/^bgp community-list (.+)$/) { - push(@{$config->{'frr_bgp_community_list'}}, $line); - 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/^ipv6 prefix-list (.+) seq (\d+) (.*)$/) { - $config->{'frr_prefix_list_v6'}->{$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); - } + $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/^interface (.+)$/) { + $section = \$config->{'frr_interfaces'}->{$1}; + next; + } elsif ($line =~ m/^bgp community-list (.+)$/) { + push(@{ $config->{'frr_bgp_community_list'} }, $line); + 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/^ipv6 prefix-list (.+) seq (\d+) (.*)$/) { + $config->{'frr_prefix_list_v6'}->{$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); + } } } @@ -621,31 +692,28 @@ sub reload_controller { my $bin_path = "/usr/lib/frr/frr-reload.py"; if (!-e $bin_path) { - log_warn("missing $bin_path. Please install frr-pythontools package"); - return; + log_warn("missing $bin_path. Please install frr-pythontools package"); + return; } run_command(['systemctl', 'enable', '--now', 'frr']) - if !-e "/etc/systemd/system/multi-user.target.wants/frr.service"; + if !-e "/etc/systemd/system/multi-user.target.wants/frr.service"; my $err = sub { - my $line = shift; - if ($line =~ /ERROR:/) { - warn "$line \n"; - } + 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], errfunc => $err); - }; - if ($@) { - warn "frr reload command fail. Restarting frr."; - eval { run_command(['systemctl', 'restart', 'frr']); }; - } + eval { run_command([$bin_path, '--stdout', '--reload', $conf_file], 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 index 4f3bb5c..5024bed 100644 --- a/src/PVE/Network/SDN/Controllers/FaucetPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/FaucetPlugin.pm @@ -16,8 +16,7 @@ sub type { } sub properties { - return { - }; + return {}; } # Plugin implementation @@ -30,18 +29,17 @@ 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 $dphex = printf("%x", $dpid); my $zone_config = { - dp_id => $dphex, - hardware => "Open vSwitch", - }; + 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) = @_; @@ -63,7 +61,7 @@ sub generate_controller_vnet_config { $config->{faucet}->{vlans}->{$vnetid} = $vlan_config; - push(@{$config->{faucet}->{routers}->{$zoneid}->{vlans}} , $vnetid); + push(@{ $config->{faucet}->{routers}->{$zoneid}->{vlans} }, $vnetid); } @@ -77,7 +75,7 @@ sub write_controller_config { my $frr_config_file = "/etc/faucet/faucet.yaml"; - my $writefh = IO::File->new($frr_config_file,">"); + my $writefh = IO::File->new($frr_config_file, ">"); print $writefh $rawconfig; $writefh->close(); } diff --git a/src/PVE/Network/SDN/Controllers/IsisPlugin.pm b/src/PVE/Network/SDN/Controllers/IsisPlugin.pm index 97c6876..6e3574d 100644 --- a/src/PVE/Network/SDN/Controllers/IsisPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/IsisPlugin.pm @@ -18,37 +18,40 @@ sub type { } PVE::JSONSchema::register_format('pve-sdn-isis-net', \&pve_verify_sdn_isis_net); + sub pve_verify_sdn_isis_net { my ($net) = @_; if ($net !~ m/^[a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2}$/) { - die "value does not look like a valid isis net\n"; + die "value does not look like a valid isis net\n"; } return $net; } sub properties { return { - 'isis-domain' => { - description => "ISIS domain.", - type => 'string' - }, - 'isis-ifaces' => { - description => "ISIS interface.", - type => 'string', format => 'pve-iface-list', - }, - 'isis-net' => { - description => "ISIS network entity title.", - type => 'string', format => 'pve-sdn-isis-net', - }, + 'isis-domain' => { + description => "ISIS domain.", + type => 'string', + }, + 'isis-ifaces' => { + description => "ISIS interface.", + type => 'string', + format => 'pve-iface-list', + }, + 'isis-net' => { + description => "ISIS network entity title.", + type => 'string', + format => 'pve-sdn-isis-net', + }, }; } sub options { return { - 'isis-domain' => { optional => 0 }, - 'isis-net' => { optional => 0 }, - 'isis-ifaces' => { optional => 0 }, + 'isis-domain' => { optional => 0 }, + 'isis-net' => { optional => 0 }, + 'isis-ifaces' => { optional => 0 }, 'node' => { optional => 0 }, 'loopback' => { optional => 1 }, }; @@ -67,21 +70,19 @@ sub generate_controller_config { return if $local_node ne $plugin_config->{node}; my @router_config = ( - "net $isis_net", - "redistribute ipv4 connected level-1", - "redistribute ipv6 connected level-1", - "log-adjacency-changes", + "net $isis_net", + "redistribute ipv4 connected level-1", + "redistribute ipv6 connected level-1", + "log-adjacency-changes", ); - push(@{$config->{frr}->{router}->{"isis $isis_domain"}}, @router_config); + push(@{ $config->{frr}->{router}->{"isis $isis_domain"} }, @router_config); - my @iface_config = ( - "ip router isis $isis_domain" - ); + my @iface_config = ("ip router isis $isis_domain"); my @ifaces = PVE::Tools::split_list($isis_ifaces); for my $iface (sort @ifaces) { - push(@{$config->{frr_interfaces}->{$iface}}, @iface_config); + push(@{ $config->{frr_interfaces}->{$iface} }, @iface_config); } return $config; @@ -103,7 +104,7 @@ sub on_update_hook { # 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}}) { + foreach my $id (keys %{ $controller_cfg->{ids} }) { next if $id eq $controllerid; my $controller = $controller_cfg->{ids}->{$id}; next if $controller->{type} ne "isis"; @@ -130,4 +131,3 @@ sub reload_controller { 1; - diff --git a/src/PVE/Network/SDN/Controllers/Plugin.pm b/src/PVE/Network/SDN/Controllers/Plugin.pm index d6ffc5f..3de1af7 100644 --- a/src/PVE/Network/SDN/Controllers/Plugin.pm +++ b/src/PVE/Network/SDN/Controllers/Plugin.pm @@ -10,17 +10,23 @@ use PVE::Cluster; use PVE::JSONSchema qw(get_standard_option); use base qw(PVE::SectionConfig); -PVE::Cluster::cfs_register_file('sdn/controllers.cfg', +PVE::Cluster::cfs_register_file( + 'sdn/controllers.cfg', sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_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_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) = @_; @@ -35,13 +41,16 @@ sub parse_sdn_controller_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 }), + 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 }, + ), }, }; @@ -54,11 +63,11 @@ sub parse_section_header { 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); + 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; } @@ -75,7 +84,6 @@ sub generate_controller_config { die "please implement inside plugin"; } - sub generate_controller_zone_config { my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_; @@ -135,7 +143,11 @@ sub get_router_id { die "can't autofind a router-id value from ip or mac" if !$mac; my @mac_bytes = split(':', $mac); - return hex($mac_bytes[2]).".".hex($mac_bytes[3]).".".hex($mac_bytes[4]).".".hex($mac_bytes[5]); + return + hex($mac_bytes[2]) . "." + . hex($mac_bytes[3]) . "." + . hex($mac_bytes[4]) . "." + . hex($mac_bytes[5]); } 1; diff --git a/src/PVE/Network/SDN/Dhcp.pm b/src/PVE/Network/SDN/Dhcp.pm index 8bbc210..8032b54 100644 --- a/src/PVE/Network/SDN/Dhcp.pm +++ b/src/PVE/Network/SDN/Dhcp.pm @@ -35,7 +35,7 @@ sub add_mapping { my $macdb = PVE::Network::SDN::Ipams::read_macdb(); my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcptype); - $dhcp_plugin->add_ip_mapping($zoneid, $macdb, $mac, $ip4, $ip6) + $dhcp_plugin->add_ip_mapping($zoneid, $macdb, $mac, $ip4, $ip6); } sub remove_mapping { @@ -70,60 +70,64 @@ sub regenerate_config { my $any_zone_needs_dhcp = grep { $_->{dhcp} } values $zone_cfg->{ids}->%*; foreach my $plugin_name (@$plugins) { - my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name); - eval { $plugin->before_regenerate(!$any_zone_needs_dhcp) }; - die "Could not run before_regenerate for DHCP plugin $plugin_name $@\n" if $@; + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name); + eval { $plugin->before_regenerate(!$any_zone_needs_dhcp) }; + die "Could not run before_regenerate for DHCP plugin $plugin_name $@\n" if $@; } - foreach my $zoneid (sort keys %{$zone_cfg->{ids}}) { + foreach my $zoneid (sort keys %{ $zone_cfg->{ids} }) { my $zone = $zone_cfg->{ids}->{$zoneid}; next if !$zone->{dhcp}; - my $dhcp_plugin_name = $zone->{dhcp}; - my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_plugin_name); + my $dhcp_plugin_name = $zone->{dhcp}; + my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_plugin_name); - die "Could not find DHCP plugin: $dhcp_plugin_name" if !$dhcp_plugin; + die "Could not find DHCP plugin: $dhcp_plugin_name" if !$dhcp_plugin; - eval { $dhcp_plugin->before_configure($zoneid, $zone) }; - die "Could not run before_configure for DHCP server $zoneid $@\n" if $@; + eval { $dhcp_plugin->before_configure($zoneid, $zone) }; + die "Could not run before_configure for DHCP server $zoneid $@\n" if $@; - for my $vnetid (sort keys %{$vnet_cfg->{ids}}) { - my $vnet = $vnet_cfg->{ids}->{$vnetid}; - next if $vnet->{zone} ne $zoneid; + for my $vnetid (sort keys %{ $vnet_cfg->{ids} }) { + my $vnet = $vnet_cfg->{ids}->{$vnetid}; + next if $vnet->{zone} ne $zoneid; - my $config = []; - my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid); + my $config = []; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid); - foreach my $subnet_id (sort keys %{$subnets}) { - my $subnet_config = $subnets->{$subnet_id}; - my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet_config); + foreach my $subnet_id (sort keys %{$subnets}) { + my $subnet_config = $subnets->{$subnet_id}; + my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet_config); - my ($zone, $subnet_network, $subnet_mask) = split(/-/, $subnet_id); - next if $zone ne $zoneid; + my ($zone, $subnet_network, $subnet_mask) = split(/-/, $subnet_id); + next if $zone ne $zoneid; - eval { $dhcp_plugin->configure_subnet($config, $zoneid, $vnetid, $subnet_config) }; - warn "Could not configure subnet $subnet_id: $@\n" if $@; + eval { $dhcp_plugin->configure_subnet($config, $zoneid, $vnetid, $subnet_config) }; + warn "Could not configure subnet $subnet_id: $@\n" if $@; - foreach my $dhcp_range (@$dhcp_ranges) { - eval { $dhcp_plugin->configure_range($config, $zoneid, $vnetid, $subnet_config, $dhcp_range) }; - warn "Could not configure DHCP range for $subnet_id: $@\n" if $@; - } - } + foreach my $dhcp_range (@$dhcp_ranges) { + eval { + $dhcp_plugin->configure_range( + $config, $zoneid, $vnetid, $subnet_config, $dhcp_range, + ); + }; + warn "Could not configure DHCP range for $subnet_id: $@\n" if $@; + } + } - eval { $dhcp_plugin->configure_vnet($config, $zoneid, $vnetid, $vnet) }; - warn "Could not configure vnet $vnetid: $@\n" if $@; - } + eval { $dhcp_plugin->configure_vnet($config, $zoneid, $vnetid, $vnet) }; + warn "Could not configure vnet $vnetid: $@\n" if $@; + } - eval { $dhcp_plugin->after_configure($zoneid, !$any_zone_needs_dhcp) }; - warn "Could not run after_configure for DHCP server $zoneid $@\n" if $@; + eval { $dhcp_plugin->after_configure($zoneid, !$any_zone_needs_dhcp) }; + warn "Could not run after_configure for DHCP server $zoneid $@\n" if $@; } foreach my $plugin_name (@$plugins) { - my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name); + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name); - eval { $plugin->after_regenerate() }; - warn "Could not run after_regenerate for DHCP plugin $plugin_name $@\n" if $@; + eval { $plugin->after_regenerate() }; + warn "Could not run after_regenerate for DHCP plugin $plugin_name $@\n" if $@; } } diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm index 263d24f..dd34278 100644 --- a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm @@ -26,9 +26,9 @@ my sub assert_dnsmasq_installed { my $bin_path = "/usr/sbin/dnsmasq"; if (!-e $bin_path) { - return if $noerr; # just ignore, e.g., in case zone doesn't use DHCP at all - log_warn("please install the 'dnsmasq' package in order to use the DHCP feature!"); - die "cannot reload with missing 'dnsmasq' package\n"; + return if $noerr; # just ignore, e.g., in case zone doesn't use DHCP at all + log_warn("please install the 'dnsmasq' package in order to use the DHCP feature!"); + die "cannot reload with missing 'dnsmasq' package\n"; } return 1; } @@ -43,7 +43,8 @@ sub update_lease { #update lease as ip could still be associated to an old removed mac my $bus = Net::DBus->system(); my $dnsmasq = $bus->get_service("uk.org.thekelleys.dnsmasq.$dhcpid"); - my $manager = $dnsmasq->get_object("/uk/org/thekelleys/dnsmasq","uk.org.thekelleys.dnsmasq.$dhcpid"); + my $manager = + $dnsmasq->get_object("/uk/org/thekelleys/dnsmasq", "uk.org.thekelleys.dnsmasq.$dhcpid"); my @hostname = unpack("C*", "*"); $manager->AddDhcpLease($ip4, $mac, \@hostname, undef, 0, 0, 0) if $ip4; @@ -58,62 +59,69 @@ sub add_ip_mapping { my $reload = undef; my $appendFn = sub { - open(my $in, '<', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; - open(my $out, '>', $ethers_tmp_file) or die "Could not open file '$ethers_tmp_file' $!\n"; - - my $match = undef; - - while (my $line = <$in>) { - chomp($line); - my $parsed_ip4 = undef; - my $parsed_ip6 = undef; - my ($parsed_mac, $parsed_ip1, $parsed_ip2) = split(/,/, $line); - - if ($parsed_ip2) { - $parsed_ip4 = $parsed_ip1; - $parsed_ip6 = $parsed_ip2; - } elsif (Net::IP::ip_is_ipv4($parsed_ip1)) { - $parsed_ip4 = $parsed_ip1; - } else { - $parsed_ip6 = $parsed_ip1; - } - $parsed_ip6 = $1 if $parsed_ip6 && $parsed_ip6 =~ m/\[(\S+)\]/; - - #delete changed - if (!defined($macdb->{macs}->{$parsed_mac}) || - ($parsed_ip4 && $macdb->{macs}->{$parsed_mac}->{'ip4'} && $macdb->{macs}->{$parsed_mac}->{'ip4'} ne $parsed_ip4) || - ($parsed_ip6 && $macdb->{macs}->{$parsed_mac}->{'ip6'} && $macdb->{macs}->{$parsed_mac}->{'ip6'} ne $parsed_ip6)) { - $reload = 1; - next; - } - - if ($parsed_mac eq $mac) { - $match = 1 if $ip4 && $parsed_ip4 && $ip4; - $match = 1 if $ip6 && $parsed_ip6 && $ip6; - } - - print $out "$line\n"; - } - - if(!$match) { - my $reservation = $mac; - $reservation .= ",$ip4" if $ip4; - $reservation .= ",[$ip6]" if $ip6; - print $out "$reservation\n"; - $reload = 1; - } - - close $in; - close $out; - move $ethers_tmp_file, $ethers_file; - chmod 0644, $ethers_file; + open(my $in, '<', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + open(my $out, '>', $ethers_tmp_file) + or die "Could not open file '$ethers_tmp_file' $!\n"; + + my $match = undef; + + while (my $line = <$in>) { + chomp($line); + my $parsed_ip4 = undef; + my $parsed_ip6 = undef; + my ($parsed_mac, $parsed_ip1, $parsed_ip2) = split(/,/, $line); + + if ($parsed_ip2) { + $parsed_ip4 = $parsed_ip1; + $parsed_ip6 = $parsed_ip2; + } elsif (Net::IP::ip_is_ipv4($parsed_ip1)) { + $parsed_ip4 = $parsed_ip1; + } else { + $parsed_ip6 = $parsed_ip1; + } + $parsed_ip6 = $1 if $parsed_ip6 && $parsed_ip6 =~ m/\[(\S+)\]/; + + #delete changed + if ( + !defined($macdb->{macs}->{$parsed_mac}) + || ($parsed_ip4 + && $macdb->{macs}->{$parsed_mac}->{'ip4'} + && $macdb->{macs}->{$parsed_mac}->{'ip4'} ne $parsed_ip4) + || ($parsed_ip6 + && $macdb->{macs}->{$parsed_mac}->{'ip6'} + && $macdb->{macs}->{$parsed_mac}->{'ip6'} ne $parsed_ip6) + ) { + $reload = 1; + next; + } + + if ($parsed_mac eq $mac) { + $match = 1 if $ip4 && $parsed_ip4 && $ip4; + $match = 1 if $ip6 && $parsed_ip6 && $ip6; + } + + print $out "$line\n"; + } + + if (!$match) { + my $reservation = $mac; + $reservation .= ",$ip4" if $ip4; + $reservation .= ",[$ip6]" if $ip6; + print $out "$reservation\n"; + $reload = 1; + } + + close $in; + close $out; + move $ethers_tmp_file, $ethers_file; + chmod 0644, $ethers_file; }; PVE::Tools::lock_file($ethers_file, 10, $appendFn); if ($@) { - warn "Unable to add $mac to the dnsmasq configuration: $@\n"; - return; + warn "Unable to add $mac to the dnsmasq configuration: $@\n"; + return; } my $service_name = "dnsmasq\@$dhcpid"; @@ -125,29 +133,30 @@ sub configure_subnet { my ($class, $config, $dhcpid, $vnetid, $subnet_config) = @_; die "No gateway defined for subnet $subnet_config->{id}" - if !$subnet_config->{gateway}; + if !$subnet_config->{gateway}; my $tag = $subnet_config->{id}; my ($zone, $network, $mask) = split(/-/, $tag); if (Net::IP::ip_is_ipv4($network)) { - $mask = (2 ** $mask - 1) << (32 - $mask); - $mask = join( '.', unpack( "C4", pack( "N", $mask ) ) ); + $mask = (2**$mask - 1) << (32 - $mask); + $mask = join('.', unpack("C4", pack("N", $mask))); } push @{$config}, "dhcp-range=set:$tag,$network,static,$mask,infinite"; my $option_string; if (ip_is_ipv6($subnet_config->{network})) { - $option_string = 'option6'; + $option_string = 'option6'; } else { - $option_string = 'option'; - push @{$config}, "dhcp-option=tag:$tag,$option_string:router,$subnet_config->{gateway}"; + $option_string = 'option'; + push @{$config}, "dhcp-option=tag:$tag,$option_string:router,$subnet_config->{gateway}"; } - push @{$config}, "dhcp-option=tag:$tag,$option_string:dns-server,$subnet_config->{'dhcp-dns-server'}" - if $subnet_config->{'dhcp-dns-server'}; + push @{$config}, + "dhcp-option=tag:$tag,$option_string:dns-server,$subnet_config->{'dhcp-dns-server'}" + if $subnet_config->{'dhcp-dns-server'}; } sub configure_range { @@ -162,8 +171,8 @@ sub configure_vnet { push @{$config}, "interface=$vnetid"; PVE::Tools::file_set_contents( - "$DNSMASQ_CONFIG_ROOT/$dhcpid/10-$vnetid.conf", - join("\n", @{$config}) . "\n" + "$DNSMASQ_CONFIG_ROOT/$dhcpid/10-$vnetid.conf", + join("\n", @{$config}) . "\n", ); } @@ -197,8 +206,7 @@ sub before_configure { DBUSCFG PVE::Tools::file_set_contents( - "/etc/dbus-1/system.d/dnsmasq.$dhcpid.conf", - $dbus_config + "/etc/dbus-1/system.d/dnsmasq.$dhcpid.conf", $dbus_config, ); my $config_directory = "$DNSMASQ_CONFIG_ROOT/$dhcpid"; @@ -211,8 +219,7 @@ DNSMASQ_OPTS="--conf-file=/dev/null --enable-dbus=uk.org.thekelleys.dnsmasq.$dhc CFG PVE::Tools::file_set_contents( - "$DNSMASQ_DEFAULT_ROOT/dnsmasq.$dhcpid", - $default_config + "$DNSMASQ_DEFAULT_ROOT/dnsmasq.$dhcpid", $default_config, ); my $mtu = PVE::Network::SDN::Zones::get_mtu($zone_cfg); @@ -245,15 +252,18 @@ dhcp-ignore-names=tag:wpad-ignore CFG PVE::Tools::file_set_contents( - "$config_directory/00-default.conf", - $default_dnsmasq_config + "$config_directory/00-default.conf", $default_dnsmasq_config, ); my @config_files = (); - PVE::Tools::dir_glob_foreach($config_directory, '10-.*\.conf', sub { - my ($file) = @_; - push @config_files, "$config_directory/$file"; - }); + PVE::Tools::dir_glob_foreach( + $config_directory, + '10-.*\.conf', + sub { + my ($file) = @_; + push @config_files, "$config_directory/$file"; + }, + ); unlink @config_files; } diff --git a/src/PVE/Network/SDN/Dhcp/Plugin.pm b/src/PVE/Network/SDN/Dhcp/Plugin.pm index 1757915..b5d32fa 100644 --- a/src/PVE/Network/SDN/Dhcp/Plugin.pm +++ b/src/PVE/Network/SDN/Dhcp/Plugin.pm @@ -10,11 +10,11 @@ use base qw(PVE::SectionConfig); my $defaultData = { propertyList => { - type => { - description => "Plugin type.", - format => 'pve-configid', - type => 'string', - }, + type => { + description => "Plugin type.", + format => 'pve-configid', + type => 'string', + }, }, }; diff --git a/src/PVE/Network/SDN/Dns.pm b/src/PVE/Network/SDN/Dns.pm index 581b467..76833ea 100644 --- a/src/PVE/Network/SDN/Dns.pm +++ b/src/PVE/Network/SDN/Dns.pm @@ -15,7 +15,6 @@ 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) = @_; @@ -41,7 +40,7 @@ sub write_config { sub sdn_dns_ids { my ($cfg) = @_; - return keys %{$cfg->{ids}}; + return keys %{ $cfg->{ids} }; } sub complete_sdn_dns { @@ -49,7 +48,7 @@ sub complete_sdn_dns { my $cfg = PVE::Network::SDN::Dns::config(); - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Dns::sdn_dns_ids($cfg) ]; + return $cmdname eq 'add' ? [] : [PVE::Network::SDN::Dns::sdn_dns_ids($cfg)]; } 1; diff --git a/src/PVE/Network/SDN/Dns/Plugin.pm b/src/PVE/Network/SDN/Dns/Plugin.pm index e5daa7d..2864d4c 100644 --- a/src/PVE/Network/SDN/Dns/Plugin.pm +++ b/src/PVE/Network/SDN/Dns/Plugin.pm @@ -12,22 +12,29 @@ use LWP::UserAgent; 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::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 undef if $noerr; + die "dns ID '$id' contains illegal characters\n"; } return $id; } @@ -35,15 +42,18 @@ sub parse_sdn_dns_id { my $defaultData = { propertyList => { - type => { - description => "Plugin type.", - type => 'string', format => 'pve-configid', - }, + 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 }), - fingerprint => get_standard_option('fingerprint-sha256', { optional => 1 }), + dns => get_standard_option( + 'pve-sdn-dns-id', + { completion => \&PVE::Network::SDN::Dns::complete_sdn_dns }, + ), + fingerprint => get_standard_option('fingerprint-sha256', { optional => 1 }), }, }; @@ -56,16 +66,15 @@ sub parse_section_header { 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); + 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) = @_; diff --git a/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm index aad8b6f..dfb7932 100644 --- a/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm +++ b/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm @@ -19,28 +19,28 @@ sub type { sub properties { return { - url => { - type => 'string', - }, - key => { - type => 'string', - }, - reversemaskv6 => { - type => 'integer' - }, + 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.", - }, - fingerprint => { optional => 1 }, + url => { optional => 0 }, + key => { optional => 0 }, + ttl => { optional => 1 }, + reversemaskv6 => { + optional => 1, + description => "force a different netmask for the ipv6 reverse zone name.", + }, + fingerprint => { optional => 1 }, }; } @@ -48,11 +48,11 @@ my sub powerdns_api_request { my ($config, $method, $path, $params) = @_; return PVE::Network::SDN::api_request( - $method, - "$config->{url}${path}", - ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $config->{key}], - $params, - $config->{fingerprint}, + $method, + "$config->{url}${path}", + ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $config->{key}], + $params, + $config->{fingerprint}, ); } @@ -63,35 +63,35 @@ sub add_a_record { my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400; my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A"; - my $fqdn = $hostname.".".$zone."."; + my $fqdn = $hostname . "." . $zone . "."; my $zonecontent = get_zone_content($plugin_config, $zone); my $existing_rrset = get_zone_rrset($zonecontent, $fqdn, $type); my $final_records = []; - for my $record (@{$existing_rrset->{records}}) { - if ($record->{content} eq $ip) { - return; # the record already exist so return early - } - push @$final_records, $record; + for my $record (@{ $existing_rrset->{records} }) { + if ($record->{content} eq $ip) { + return; # the record already exist so return early + } + push @$final_records, $record; } my $record = { - content => $ip, - disabled => JSON::false, - name => $fqdn, - type => $type, + content => $ip, + disabled => JSON::false, + name => $fqdn, + type => $type, }; push @$final_records, $record; my $params = { - rrsets => [{ - name => $fqdn, - type => $type, - ttl => $ttl, - changetype => "REPLACE", - records => $final_records, - }], + rrsets => [{ + name => $fqdn, + type => $type, + ttl => $ttl, + changetype => "REPLACE", + records => $final_records, + }], }; eval { powerdns_api_request($plugin_config, 'PATCH', "/zones/$zone", $params) }; @@ -109,20 +109,20 @@ sub add_ptr_record { my $type = "PTR"; my $record = { - content => $hostname, - disabled => JSON::false, - name => $reverseip, - type => $type, + content => $hostname, + disabled => JSON::false, + name => $reverseip, + type => $type, }; my $params = { - rrsets => [{ - name => $reverseip, - type => $type, - ttl => $ttl, - changetype => "REPLACE", - records => [ $record ], - }], + rrsets => [{ + name => $reverseip, + type => $type, + ttl => $ttl, + changetype => "REPLACE", + records => [$record], + }], }; eval { powerdns_api_request($plugin_config, 'PATCH', "/zones/$zone", $params) }; @@ -132,33 +132,33 @@ sub add_ptr_record { sub del_a_record { my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_; - my $fqdn = $hostname.".".$zone."."; + 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, $type); - my $final_records = [ grep { $_->{content} ne $ip } $existing_rrset->{records}->@* ]; + my $final_records = [grep { $_->{content} ne $ip } $existing_rrset->{records}->@*]; my $final_records_size = scalar($final_records->@*); # early return if we didn't find our record (i.e., un/filtered record sets have the same size) return if scalar($existing_rrset->{records}->@*) == $final_records_size; my $rrset = { - name => $fqdn, - type => $type, + name => $fqdn, + type => $type, }; if ($final_records_size > 0) { - # if we still have other records, we rewrite them with the $ip removed - $rrset->{ttl} = $existing_rrset->{ttl}; - $rrset->{changetype} = "REPLACE"; - $rrset->{records} = $final_records; + # if we still have other records, we rewrite them with the $ip removed + $rrset->{ttl} = $existing_rrset->{ttl}; + $rrset->{changetype} = "REPLACE"; + $rrset->{records} = $final_records; } else { - $rrset->{changetype} = "DELETE"; - $rrset->{records} = []; + $rrset->{changetype} = "DELETE"; + $rrset->{records} = []; } - my $params = { rrsets => [ $rrset ] }; + my $params = { rrsets => [$rrset] }; eval { powerdns_api_request($plugin_config, 'PATCH', "/zones/$zone", $params) }; die "error delete $fqdn from zone $zone: $@" if $@ && !$noerr; @@ -172,12 +172,12 @@ sub del_ptr_record { my $type = "PTR"; my $params = { - rrsets => [{ - name => $reverseip, - type => $type, - changetype => "DELETE", - records => [], - }], + rrsets => [{ + name => $reverseip, + type => $type, + changetype => "DELETE", + records => [], + }], }; eval { powerdns_api_request($plugin_config, 'PATCH', "/zones/$zone", $params) }; @@ -201,47 +201,46 @@ sub get_reversedns_zone { my $zone = ""; if (Net::IP::ip_is_ipv4($ip)) { - my ($ipblock1, $ipblock2, $ipblock3, $ipblock4) = split(/\./, $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."; - } - } + #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(); + $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) = @_; @@ -250,7 +249,6 @@ sub on_update_hook { die "dns api error: $@" if $@; } - sub get_zone_content { my ($plugin_config, $zone) = @_; @@ -264,12 +262,11 @@ sub get_zone_content { sub get_zone_rrset { my ($zonecontent, $name, $type) = @_; - for my $rrset (@{$zonecontent->{rrsets}}) { - return $rrset if $rrset->{name} eq $name and ($rrset->{type} eq $type); + for my $rrset (@{ $zonecontent->{rrsets} }) { + return $rrset if $rrset->{name} eq $name and ($rrset->{type} eq $type); } return; # not found } 1; - diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm index c689b8f..7ec0f06 100644 --- a/src/PVE/Network/SDN/Ipams.pm +++ b/src/PVE/Network/SDN/Ipams.pm @@ -26,24 +26,26 @@ my $macdb_filename_legacy = 'priv/macs.db'; cfs_register_file( $macdb_filename, sub { - my ($filename , $data) = @_; - if (defined($data)) { - return json_reader($filename, $data); - } else { - # TODO: remove legacy cache file handling with PVE 9+ after ensuring all call sites got - # switched over. - return cfs_read_file($macdb_filename_legacy); - } + my ($filename, $data) = @_; + if (defined($data)) { + return json_reader($filename, $data); + } else { + # TODO: remove legacy cache file handling with PVE 9+ after ensuring all call sites got + # switched over. + return cfs_read_file($macdb_filename_legacy); + } }, sub { - my ($filename , $data) = @_; - # TODO: remove below with PVE 9+, add a pve8to9 check to allow doing so. - if (-e $macdb_filename_legacy && -e $macdb_filename) { - # only clean-up if we succeeded to write the new path at least once - unlink $macdb_filename_legacy or $!{ENOENT} or warn "failed to unlink legacy MAC cache - $!\n"; - } - return json_writer->($filename, $data); - } + my ($filename, $data) = @_; + # TODO: remove below with PVE 9+, add a pve8to9 check to allow doing so. + if (-e $macdb_filename_legacy && -e $macdb_filename) { + # only clean-up if we succeeded to write the new path at least once + unlink $macdb_filename_legacy + or $!{ENOENT} + or warn "failed to unlink legacy MAC cache - $!\n"; + } + return json_writer->($filename, $data); + }, ); # drop reading $macdb_filename_legacy with PVE 9+ - for now do not write it anymore. @@ -76,31 +78,40 @@ sub write_macdb { sub add_cache_mac_ip { my ($mac, $ip) = @_; - cfs_lock_file($macdb_filename, undef, sub { - my $db = read_macdb(); - if (Net::IP::ip_is_ipv4($ip)) { - $db->{macs}->{$mac}->{ip4} = $ip; - } else { - $db->{macs}->{$mac}->{ip6} = $ip; - } - write_macdb($db); - }); + cfs_lock_file( + $macdb_filename, + undef, + sub { + my $db = read_macdb(); + if (Net::IP::ip_is_ipv4($ip)) { + $db->{macs}->{$mac}->{ip4} = $ip; + } else { + $db->{macs}->{$mac}->{ip6} = $ip; + } + write_macdb($db); + }, + ); warn "$@" if $@; } sub del_cache_mac_ip { my ($mac, $ip) = @_; - cfs_lock_file($macdb_filename, undef, sub { - my $db = read_macdb(); - if (Net::IP::ip_is_ipv4($ip)) { - delete $db->{macs}->{$mac}->{ip4}; - } else { - delete $db->{macs}->{$mac}->{ip6}; - } - delete $db->{macs}->{$mac} if !defined($db->{macs}->{$mac}->{ip4}) && !defined($db->{macs}->{$mac}->{ip6}); - write_macdb($db); - }); + cfs_lock_file( + $macdb_filename, + undef, + sub { + my $db = read_macdb(); + if (Net::IP::ip_is_ipv4($ip)) { + delete $db->{macs}->{$mac}->{ip4}; + } else { + delete $db->{macs}->{$mac}->{ip6}; + } + delete $db->{macs}->{$mac} + if !defined($db->{macs}->{$mac}->{ip4}) && !defined($db->{macs}->{$mac}->{ip6}); + write_macdb($db); + }, + ); warn "$@" if $@; } @@ -138,7 +149,7 @@ sub write_config { sub sdn_ipams_ids { my ($cfg) = @_; - return keys %{$cfg->{ids}}; + return keys %{ $cfg->{ids} }; } sub complete_sdn_vnet { @@ -146,7 +157,7 @@ sub complete_sdn_vnet { my $cfg = PVE::Network::SDN::Ipams::config(); - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ]; + return $cmdname eq 'add' ? [] : [PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg)]; } sub get_ips_from_mac { @@ -157,7 +168,8 @@ sub get_ips_from_mac { my $plugin_config = get_plugin_config($zone); my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); - ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6}) = $plugin->get_ips_from_mac($plugin_config, $mac, $zoneid); + ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6}) = + $plugin->get_ips_from_mac($plugin_config, $mac, $zoneid); write_macdb($macdb); diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm index 97a0c3f..e118d03 100644 --- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm +++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm @@ -13,13 +13,12 @@ sub type { } sub properties { - return { - }; + return {}; } sub options { return { - url => { optional => 0}, + url => { optional => 0 }, token => { optional => 0 }, fingerprint => { optional => 1 }, }; @@ -29,14 +28,14 @@ sub netbox_api_request { my ($config, $method, $path, $params) = @_; return PVE::Network::SDN::api_request( - $method, - "$config->{url}${path}", - [ - 'Content-Type' => 'application/json; charset=UTF-8', - 'Authorization' => "token $config->{token}" - ], - $params, - $config->{fingerprint}, + $method, + "$config->{url}${path}", + [ + 'Content-Type' => 'application/json; charset=UTF-8', + 'Authorization' => "token $config->{token}", + ], + $params, + $config->{fingerprint}, ); } @@ -44,14 +43,20 @@ sub add_dhcp_range { my ($config, $dhcp_range, $noerr) = @_; my $result = eval { - netbox_api_request($config, "POST", "/ipam/ip-ranges/", { - start_address => $dhcp_range->{'start-address'}, - end_address => $dhcp_range->{'end-address'}, - }); + netbox_api_request( + $config, + "POST", + "/ipam/ip-ranges/", + { + start_address => $dhcp_range->{'start-address'}, + end_address => $dhcp_range->{'end-address'}, + }, + ); }; if ($@) { - return if $noerr; - die "could not create ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}: $@"; + return if $noerr; + die + "could not create ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}: $@"; } return $result->{id}; @@ -60,9 +65,7 @@ sub add_dhcp_range { sub del_dhcp_range { my ($config, $id, $noerr) = @_; - eval { - netbox_api_request($config, "DELETE", "/ipam/ip-ranges/$id/"); - }; + eval { netbox_api_request($config, "DELETE", "/ipam/ip-ranges/$id/"); }; die "could not create dhcp range: $@" if $@ && !$noerr; } @@ -76,23 +79,19 @@ sub add_subnet { my $gateway = $subnet->{gateway}; if (get_prefix_id($plugin_config, $cidr, $noerr)) { - return if $noerr; - die "prefix $cidr already exists in netbox"; + return if $noerr; + die "prefix $cidr already exists in netbox"; } - eval { - netbox_api_request($plugin_config, "POST", "/ipam/prefixes/", { - prefix => $cidr - }); - }; + eval { netbox_api_request($plugin_config, "POST", "/ipam/prefixes/", { prefix => $cidr }); }; if ($@) { - return if $noerr; - die "error adding subnet to ipam: $@"; + return if $noerr; + die "error adding subnet to ipam: $@"; } my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet); for my $dhcp_range (@$dhcp_ranges) { - add_dhcp_range($plugin_config, $dhcp_range, $noerr); + add_dhcp_range($plugin_config, $dhcp_range, $noerr); } } @@ -105,48 +104,49 @@ sub update_subnet { my $new_dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet); my $hash_range = sub { - my ($dhcp_range) = @_; - "$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}" + my ($dhcp_range) = @_; + "$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}"; }; my $old_lookup = {}; for my $dhcp_range (@$old_dhcp_ranges) { - my $hash = $hash_range->($dhcp_range); - $old_lookup->{$hash} = undef; + my $hash = $hash_range->($dhcp_range); + $old_lookup->{$hash} = undef; } my $new_lookup = {}; for my $dhcp_range (@$new_dhcp_ranges) { - my $hash = $hash_range->($dhcp_range); - $new_lookup->{$hash} = undef; + my $hash = $hash_range->($dhcp_range); + $new_lookup->{$hash} = undef; } my $to_delete_ids = (); # delete first so we don't get errors with overlapping ranges for my $dhcp_range (@$old_dhcp_ranges) { - my $hash = $hash_range->($dhcp_range); + my $hash = $hash_range->($dhcp_range); - if (exists($new_lookup->{$hash})) { - next; - } + if (exists($new_lookup->{$hash})) { + next; + } - my $internalid = get_iprange_id($plugin_config, $dhcp_range, $noerr); + my $internalid = get_iprange_id($plugin_config, $dhcp_range, $noerr); - # definedness check, because ID could be 0 - if (!defined($internalid)) { - warn "could not find id for ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}"; - next; - } + # definedness check, because ID could be 0 + if (!defined($internalid)) { + warn + "could not find id for ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}"; + next; + } - del_dhcp_range($plugin_config, $internalid, $noerr); + del_dhcp_range($plugin_config, $internalid, $noerr); } for my $dhcp_range (@$new_dhcp_ranges) { - my $hash = $hash_range->($dhcp_range); + my $hash = $hash_range->($dhcp_range); - add_dhcp_range($plugin_config, $dhcp_range, $noerr) - if !exists($old_lookup->{$hash}); + add_dhcp_range($plugin_config, $dhcp_range, $noerr) + if !exists($old_lookup->{$hash}); } } @@ -159,105 +159,126 @@ sub del_subnet { # definedness check, because ID could be 0 if (!defined($internalid)) { - warn "could not find id for ip prefix $cidr"; - return; + warn "could not find id for ip prefix $cidr"; + return; } if (!is_prefix_empty($plugin_config, $cidr, $noerr)) { - return if $noerr; - die "not deleting prefix $cidr because it still contains entries"; + return if $noerr; + die "not deleting prefix $cidr because it still contains entries"; } # last IP is assumed to be the gateway, delete it if (!$class->del_ip($plugin_config, $subnetid, $subnet, $subnet->{gateway}, $noerr)) { - return if $noerr; - die "could not delete gateway ip from subnet $subnetid"; + return if $noerr; + die "could not delete gateway ip from subnet $subnetid"; } my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet); for my $dhcp_range (@$dhcp_ranges) { - my $internalid = get_iprange_id($plugin_config, $dhcp_range, $noerr); + my $internalid = get_iprange_id($plugin_config, $dhcp_range, $noerr); - # definedness check, because ID could be 0 - if (!defined($internalid)) { - warn "could not find id for ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}"; - next; - } + # definedness check, because ID could be 0 + if (!defined($internalid)) { + warn + "could not find id for ip range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}"; + next; + } - del_dhcp_range($plugin_config, $internalid, $noerr); + del_dhcp_range($plugin_config, $internalid, $noerr); } - eval { - netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/"); - }; + eval { netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/"); }; die "error deleting subnet from ipam: $@" if $@ && !$noerr; } sub add_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_; + my ( + $class, + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $vmid, + $is_gateway, + $noerr, + ) = @_; my $mask = $subnet->{mask}; my $description = undef; if ($is_gateway) { - $description = 'gateway' + $description = 'gateway'; } elsif ($mac) { - $description = "mac:$mac"; + $description = "mac:$mac"; } eval { - my $params = { - address => "$ip/$mask", - description => $description, - }; + my $params = { + address => "$ip/$mask", + description => $description, + }; - $params->{dns_name} = $hostname if $hostname; + $params->{dns_name} = $hostname if $hostname; - netbox_api_request($plugin_config, "POST", "/ipam/ip-addresses/", $params); + netbox_api_request($plugin_config, "POST", "/ipam/ip-addresses/", $params); }; if ($@) { - if ($is_gateway) { - die "error add subnet ip to ipam: ip $ip already exist: $@" - if !is_ip_gateway($plugin_config, $ip, $noerr); - } elsif (!$noerr) { - die "error add subnet ip to ipam: ip already exist: $@"; - } + if ($is_gateway) { + die "error add subnet ip to ipam: ip $ip already exist: $@" + if !is_ip_gateway($plugin_config, $ip, $noerr); + } elsif (!$noerr) { + die "error add subnet ip to ipam: ip already exist: $@"; + } } } sub update_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_; + my ( + $class, + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $vmid, + $is_gateway, + $noerr, + ) = @_; my $mask = $subnet->{mask}; my $description = undef; if ($is_gateway) { - $description = 'gateway' + $description = 'gateway'; } elsif ($mac) { - $description = "mac:$mac"; + $description = "mac:$mac"; } my $ip_id = get_ip_id($plugin_config, $ip, $noerr); # definedness check, because ID could be 0 if (!defined($ip_id)) { - return if $noerr; - die "could not find id for ip address $ip"; + return if $noerr; + die "could not find id for ip address $ip"; } eval { - my $params = { - address => "$ip/$mask", - description => $description, - }; + my $params = { + address => "$ip/$mask", + description => $description, + }; - $params->{dns_name} = $hostname if $hostname; + $params->{dns_name} = $hostname if $hostname; - netbox_api_request($plugin_config, "PATCH", "/ipam/ip-addresses/$ip_id/", $params); + netbox_api_request($plugin_config, "PATCH", "/ipam/ip-addresses/$ip_id/", $params); }; if ($@) { - die "error update ip $ip : $@" if !$noerr; + die "error update ip $ip : $@" if !$noerr; } } @@ -270,28 +291,30 @@ sub add_next_freeip { # definedness check, because ID could be 0 if (!defined($internalid)) { - return if $noerr; - die "could not find id for prefix $cidr"; + return if $noerr; + die "could not find id for prefix $cidr"; } my $description = undef; $description = "mac:$mac" if $mac; my $ip = eval { - my $params = { - description => $description, - }; + my $params = { + description => $description, + }; - $params->{dns_name} = $hostname if $hostname; + $params->{dns_name} = $hostname if $hostname; - my $result = netbox_api_request($plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", $params); + my $result = netbox_api_request( + $plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", $params, + ); - my ($ip, undef) = split(/\//, $result->{address}); - return $ip; + my ($ip, undef) = split(/\//, $result->{address}); + return $ip; }; if ($@) { - die "can't find free ip in subnet $cidr: $@" if !$noerr; + die "can't find free ip in subnet $cidr: $@" if !$noerr; } return $ip; @@ -304,29 +327,33 @@ sub add_range_next_freeip { # definedness check, because ID could be 0 if (!defined($internalid)) { - return if $noerr; - die "could not find id for ip range $range->{'start-address'}:$range->{'end-address'}"; + return if $noerr; + die "could not find id for ip range $range->{'start-address'}:$range->{'end-address'}"; } my $description = undef; $description = "mac:$data->{mac}" if $data->{mac}; my $ip = eval { - my $params = { - description => $description, - }; + my $params = { + description => $description, + }; - $params->{dns_name} = $data->{hostname} if $data->{hostname}; + $params->{dns_name} = $data->{hostname} if $data->{hostname}; - my $result = netbox_api_request($plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", $params); + my $result = netbox_api_request( + $plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", $params, + ); - my ($ip, undef) = split(/\//, $result->{address}); - print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" if $ip; - return $ip; + my ($ip, undef) = split(/\//, $result->{address}); + print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" + if $ip; + return $ip; }; if ($@) { - die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr; + die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" + if !$noerr; } return $ip; @@ -339,15 +366,13 @@ sub del_ip { my $ip_id = get_ip_id($plugin_config, $ip, $noerr); if (!defined($ip_id)) { - warn "could not find id for ip $ip"; - return; + warn "could not find id for ip $ip"; + return; } - eval { - netbox_api_request($plugin_config, "DELETE", "/ipam/ip-addresses/$ip_id/"); - }; + eval { netbox_api_request($plugin_config, "DELETE", "/ipam/ip-addresses/$ip_id/"); }; if ($@) { - die "error delete ip $ip : $@" if !$noerr; + die "error delete ip $ip : $@" if !$noerr; } return 1; @@ -360,21 +385,21 @@ sub get_ips_from_mac { my $ip6 = undef; my $data = eval { - netbox_api_request($plugin_config, "GET", "/ipam/ip-addresses/?description__ic=$mac"); + netbox_api_request($plugin_config, "GET", "/ipam/ip-addresses/?description__ic=$mac"); }; if ($@) { - return if $noerr; - die "could not query ip address entry for mac $mac: $@"; + return if $noerr; + die "could not query ip address entry for mac $mac: $@"; } - for my $ip (@{$data->{results}}) { - if ($ip->{family}->{value} == 4 && !$ip4) { - ($ip4, undef) = split(/\//, $ip->{address}); - } + for my $ip (@{ $data->{results} }) { + if ($ip->{family}->{value} == 4 && !$ip4) { + ($ip4, undef) = split(/\//, $ip->{address}); + } - if ($ip->{family}->{value} == 6 && !$ip6) { - ($ip6, undef) = split(/\//, $ip->{address}); - } + if ($ip->{family}->{value} == 6 && !$ip6) { + ($ip6, undef) = split(/\//, $ip->{address}); + } } return ($ip4, $ip6); @@ -385,7 +410,7 @@ sub verify_api { eval { netbox_api_request($plugin_config, "GET", "/ipam/aggregates/"); }; if ($@) { - die "Can't connect to netbox api: $@"; + die "Can't connect to netbox api: $@"; } } @@ -404,11 +429,11 @@ sub get_prefix_id { my $result = eval { netbox_api_request($config, "GET", "/ipam/prefixes/?q=$ip") }; if ($@) { - return if $noerr; - die "could not obtain ID for prefix $cidr: $@"; + return if $noerr; + die "could not obtain ID for prefix $cidr: $@"; } - my $data = @{$result->{results}}[0]; + my $data = @{ $result->{results} }[0]; return $data->{id}; } @@ -416,18 +441,19 @@ sub get_iprange_id { my ($config, $range, $noerr) = @_; my $result = eval { - netbox_api_request( - $config, - "GET", - "/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}", - ); + netbox_api_request( + $config, + "GET", + "/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}", + ); }; if ($@) { - return if $noerr; - die "could not obtain ID for IP range $range->{'start-address'}:$range->{'end-address'}: $@"; + return if $noerr; + die + "could not obtain ID for IP range $range->{'start-address'}:$range->{'end-address'}: $@"; } - my $data = @{$result->{results}}[0]; + my $data = @{ $result->{results} }[0]; return $data->{id}; } @@ -436,11 +462,11 @@ sub get_ip_id { my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip") }; if ($@) { - return if $noerr; - die "could not obtain ID for IP $ip: $@"; + return if $noerr; + die "could not obtain ID for IP $ip: $@"; } - my $data = @{$result->{results}}[0]; + my $data = @{ $result->{results} }[0]; return $data->{id}; } @@ -449,11 +475,11 @@ sub is_ip_gateway { my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip") }; if ($@) { - return if $noerr; - die "could not obtain ipam entry for address $ip: $@"; + return if $noerr; + die "could not obtain ipam entry for address $ip: $@"; } - my $data = @{$result->{data}}[0]; + my $data = @{ $result->{data} }[0]; return $data->{description} eq 'gateway'; } @@ -462,14 +488,13 @@ sub is_prefix_empty { my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?parent=$cidr") }; if ($@) { - return if $noerr; - die "could not query children for prefix $cidr: $@"; + return if $noerr; + die "could not query children for prefix $cidr: $@"; } # checking against 1, because we do not count the gateway - return scalar(@{$result->{results}}) <= 1; + return scalar(@{ $result->{results} }) <= 1; } 1; - diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm index 5e72e65..6764d79 100644 --- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm +++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm @@ -13,30 +13,31 @@ use Digest::SHA; use base('PVE::Network::SDN::Ipams::Plugin'); - my $ipamdb_file = "sdn/pve-ipam-state.json"; my $ipamdb_file_legacy = "priv/ipam.db"; PVE::Cluster::cfs_register_file( $ipamdb_file, sub { - my ($filename, $data) = @_; - if (defined($data)) { - return PVE::Network::SDN::Ipams::PVEPlugin->parse_config($filename, $data); - } else { - # TODO: remove legacy state file handling with PVE 9+ after ensuring all call sites got - # switched over. - return cfs_read_file($ipamdb_file_legacy); - } + my ($filename, $data) = @_; + if (defined($data)) { + return PVE::Network::SDN::Ipams::PVEPlugin->parse_config($filename, $data); + } else { + # TODO: remove legacy state file handling with PVE 9+ after ensuring all call sites got + # switched over. + return cfs_read_file($ipamdb_file_legacy); + } }, sub { - my ($filename, $data) = @_; - # TODO: remove below with PVE 9+, add a pve8to9 check to allow doing so. - if (-e $ipamdb_file_legacy && -e $ipamdb_file) { - # only clean-up if we succeeded to write the new path at least once - unlink $ipamdb_file_legacy or $!{ENOENT} or warn "failed to unlink legacy IPAM DB - $!\n"; - } - return PVE::Network::SDN::Ipams::PVEPlugin->write_config($filename, $data); + my ($filename, $data) = @_; + # TODO: remove below with PVE 9+, add a pve8to9 check to allow doing so. + if (-e $ipamdb_file_legacy && -e $ipamdb_file) { + # only clean-up if we succeeded to write the new path at least once + unlink $ipamdb_file_legacy + or $!{ENOENT} + or warn "failed to unlink legacy IPAM DB - $!\n"; + } + return PVE::Network::SDN::Ipams::PVEPlugin->write_config($filename, $data); }, ); @@ -65,20 +66,23 @@ sub add_subnet { 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); - } - }); + 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 $@; } @@ -90,12 +94,14 @@ sub update_subnet { sub only_gateway_remains { my ($ips) = @_; - if (keys %{$ips} == 1 && - (values %{$ips})[0]->{gateway} == 1) { - return 1; + if ( + keys %{$ips} == 1 + && (values %{$ips})[0]->{gateway} == 1 + ) { + return 1; } return 0; -}; +} sub del_subnet { my ($class, $plugin_config, $subnetid, $subnet) = @_; @@ -103,25 +109,29 @@ sub del_subnet { my $cidr = $subnet->{cidr}; my $zone = $subnet->{zone}; - cfs_lock_file($ipamdb_file, undef, sub { + cfs_lock_file( + $ipamdb_file, + undef, + sub { - my $db = read_db(); + 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; + 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; - my $ips = $dbsubnet->{ips}; + my $ips = $dbsubnet->{ips}; - if (keys %{$ips} > 0 && !only_gateway_remains($ips)) { - die "cannot delete subnet '$cidr', not empty\n"; - } + if (keys %{$ips} > 0 && !only_gateway_remains($ips)) { + die "cannot delete subnet '$cidr', not empty\n"; + } - delete $dbzone->{subnets}->{$cidr}; + delete $dbzone->{subnets}->{$cidr}; - write_db($db); - }); + write_db($db); + }, + ); die "$@" if $@; } @@ -132,29 +142,37 @@ sub add_ip { 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})); - - my $data = {}; - if ($is_gateway) { - $data->{gateway} = 1; - } else { - $data->{vmid} = $vmid if $vmid; - $data->{hostname} = $hostname if $hostname; - $data->{mac} = $mac if $mac; - } - - $dbsubnet->{ips}->{$ip} = $data; - - write_db($db); - }); + 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})); + + my $data = {}; + if ($is_gateway) { + $data->{gateway} = 1; + } else { + $data->{vmid} = $vmid if $vmid; + $data->{hostname} = $hostname if $hostname; + $data->{mac} = $mac if $mac; + } + + $dbsubnet->{ips}->{$ip} = $data; + + write_db($db); + }, + ); die "$@" if $@; } @@ -172,43 +190,48 @@ sub add_next_freeip { 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} = { - mac => $mac, - hostname => $hostname, - vmid => $vmid - }; - - write_db($db); - }); + 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} = { + mac => $mac, + hostname => $hostname, + vmid => $vmid, + }; + + write_db($db); + }, + ); die "$@" if $@; return $freeip; @@ -220,31 +243,35 @@ sub add_range_next_freeip { my $cidr = $subnet->{cidr}; my $zone = $subnet->{zone}; - cfs_lock_file($ipamdb_file, undef, sub { - my $db = read_db(); + 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 $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; + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; - my $ip = new Net::IP ("$range->{'start-address'} - $range->{'end-address'}") - or die "Invalid IP address(es) in Range!\n"; - my $mac = $data->{mac}; + my $ip = new Net::IP("$range->{'start-address'} - $range->{'end-address'}") + or die "Invalid IP address(es) in Range!\n"; + my $mac = $data->{mac}; - do { - my $ip_address = $ip->version() == 6 ? $ip->short() : $ip->ip(); - if (!$dbsubnet->{ips}->{$ip_address}) { - $dbsubnet->{ips}->{$ip_address} = $data; - write_db($db); + do { + my $ip_address = $ip->version() == 6 ? $ip->short() : $ip->ip(); + if (!$dbsubnet->{ips}->{$ip_address}) { + $dbsubnet->{ips}->{$ip_address} = $data; + write_db($db); - return $ip_address; - } - } while (++$ip); + return $ip_address; + } + } while (++$ip); - die "No free IP left in Range $range->{'start-address'}:$range->{'end-address'}}\n"; - }); + die "No free IP left in Range $range->{'start-address'}:$range->{'end-address'}}\n"; + }, + ); } sub del_ip { @@ -253,18 +280,22 @@ sub del_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); - }); + 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 $@; } @@ -281,21 +312,21 @@ sub get_ips_from_mac { my $dbzone = $db->{zones}->{$zoneid}; my $subnets = $dbzone->{subnets}; - for my $subnet ( keys %$subnets) { - next if Net::IP::ip_is_ipv4($subnet) && $ip4; - next if $ip6; - my $ips = $subnets->{$subnet}->{ips}; - for my $ip (keys %$ips) { - my $ipobject = $ips->{$ip}; - if ($ipobject->{mac} && $ipobject->{mac} eq $mac) { - if (Net::IP::ip_is_ipv4($ip)) { - $ip4 = $ip; - } else { - $ip6 = $ip; - } - } - } - last if $ip4 && $ip6; + for my $subnet (keys %$subnets) { + next if Net::IP::ip_is_ipv4($subnet) && $ip4; + next if $ip6; + my $ips = $subnets->{$subnet}->{ips}; + for my $ip (keys %$ips) { + my $ipobject = $ips->{$ip}; + if ($ipobject->{mac} && $ipobject->{mac} eq $mac) { + if (Net::IP::ip_is_ipv4($ip)) { + $ip4 = $ip; + } else { + $ip6 = $ip; + } + } + } + last if $ip4 && $ip6; } return ($ip4, $ip6); } @@ -323,7 +354,7 @@ sub write_config { sub parse_config { my ($class, $filename, $raw) = @_; - $raw = '{}' if !defined($raw) ||$raw eq ''; + $raw = '{}' if !defined($raw) || $raw eq ''; my $cfg = from_json($raw); return $cfg; diff --git a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm index 8ee430a..1265797 100644 --- a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm +++ b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm @@ -14,22 +14,22 @@ sub type { sub properties { return { - url => { - type => 'string', - }, - token => { - type => 'string', - }, - section => { - type => 'integer', - }, + url => { + type => 'string', + }, + token => { + type => 'string', + }, + section => { + type => 'integer', + }, }; } sub options { return { - url => { optional => 0}, + url => { optional => 0 }, token => { optional => 0 }, section => { optional => 0 }, fingerprint => { optional => 1 }, @@ -56,14 +56,18 @@ sub add_subnet { #create subnet if (!$internalid) { - my $params = { - subnet => $network, - mask => $mask, - sectionId => $section, - }; - - eval { PVE::Network::SDN::api_request("POST", "$url/subnets/", $headers, $params, $fingerprint) }; - die "error add subnet to ipam: $@" if $@ && !$noerr; + my $params = { + subnet => $network, + mask => $mask, + sectionId => $section, + }; + + eval { + PVE::Network::SDN::api_request( + "POST", "$url/subnets/", $headers, $params, $fingerprint, + ); + }; + die "error add subnet to ipam: $@" if $@ && !$noerr; } } @@ -86,12 +90,27 @@ sub del_subnet { return; #fixme: check that prefix is empty exluding gateway, before delete - eval { PVE::Network::SDN::api_request("DELETE", "$url/subnets/$internalid", $headers, undef, $fingerprint) }; + eval { + PVE::Network::SDN::api_request( + "DELETE", "$url/subnets/$internalid", $headers, undef, $fingerprint, + ); + }; 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 ( + $class, + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $description, + $is_gateway, + $noerr, + ) = @_; my $cidr = $subnet->{cidr}; my $url = $plugin_config->{url}; @@ -102,29 +121,43 @@ sub add_ip { my $internalid = get_prefix_id($url, $cidr, $headers); my $params = { - ip => $ip, - subnetId => $internalid, - hostname => $hostname, - description => $description, + 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, $fingerprint); + PVE::Network::SDN::api_request( + "POST", "$url/addresses/", $headers, $params, $fingerprint, + ); }; 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; - } + 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 ( + $class, + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $description, + $is_gateway, + $noerr, + ) = @_; my $cidr = $subnet->{cidr}; my $url = $plugin_config->{url}; @@ -136,18 +169,20 @@ sub update_ip { die "can't find ip addresse in ipam" if !$ip_id; my $params = { - hostname => $hostname, - description => $description, + 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,$fingerprint); + PVE::Network::SDN::api_request( + "PATCH", "$url/addresses/$ip_id", $headers, $params, $fingerprint, + ); }; if ($@) { - die "ipam: error update subnet ip $ip: $@" if !$noerr; + die "ipam: error update subnet ip $ip: $@" if !$noerr; } } @@ -164,16 +199,22 @@ sub add_next_freeip { my $internalid = get_prefix_id($url, $cidr, $headers); my $params = { - hostname => $hostname, - description => $description, + 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, $fingerprint); - $ip = $result->{data}; + my $result = PVE::Network::SDN::api_request( + "POST", + "$url/addresses/first_free/$internalid/", + $headers, + $params, + $fingerprint, + ); + $ip = $result->{data}; }; if ($@) { @@ -209,17 +250,18 @@ sub del_ip { return if !$ip_id; eval { - PVE::Network::SDN::api_request("DELETE", "$url/addresses/$ip_id", $headers, undef, $fingerprint); + PVE::Network::SDN::api_request( + "DELETE", "$url/addresses/$ip_id", $headers, undef, $fingerprint, + ); }; if ($@) { - die "error delete ip $ip: $@" if !$noerr; + die "error delete ip $ip: $@" if !$noerr; } } sub get_ips_from_mac { my ($class, $plugin_config, $mac, $zoneid) = @_; - my $url = $plugin_config->{url}; my $token = $plugin_config->{token}; my $fingerprint = $plugin_config->{fingerprint}; @@ -228,20 +270,28 @@ sub get_ips_from_mac { my $ip4 = undef; my $ip6 = undef; - my $ips = eval { PVE::Network::SDN::api_request("GET", "$url/addresses/search_mac/$mac", $headers, undef, $fingerprint) }; + my $ips = eval { + PVE::Network::SDN::api_request( + "GET", + "$url/addresses/search_mac/$mac", + $headers, + undef, + $fingerprint, + ); + }; return if $@; #fixme die "parsing of result not yet implemented"; for my $ip (@$ips) { -# if ($ip->{family}->{value} == 4 && !$ip4) { -# ($ip4, undef) = split(/\//, $ip->{address}); -# } -# -# if ($ip->{family}->{value} == 6 && !$ip6) { -# ($ip6, undef) = split(/\//, $ip->{address}); -# } + # if ($ip->{family}->{value} == 4 && !$ip4) { + # ($ip4, undef) = split(/\//, $ip->{address}); + # } + # + # if ($ip->{family}->{value} == 6 && !$ip6) { + # ($ip6, undef) = split(/\//, $ip->{address}); + # } } return ($ip4, $ip6); @@ -257,10 +307,12 @@ sub verify_api { my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token]; eval { - PVE::Network::SDN::api_request("GET", "$url/sections/$sectionid", $headers, undef, $fingerprint); + PVE::Network::SDN::api_request( + "GET", "$url/sections/$sectionid", $headers, undef, $fingerprint, + ); }; if ($@) { - die "Can't connect to phpipam api: $@"; + die "Can't connect to phpipam api: $@"; } } @@ -270,14 +322,13 @@ sub on_update_hook { 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 $data = @{ $result->{data} }[0]; my $internalid = $data->{id}; return $internalid; } @@ -285,7 +336,7 @@ sub get_prefix_id { 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 $data = @{ $result->{data} }[0]; my $ip_id = $data->{id}; return $ip_id; } @@ -293,11 +344,10 @@ sub get_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 $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 index 6190c24..a986a92 100644 --- a/src/PVE/Network/SDN/Ipams/Plugin.pm +++ b/src/PVE/Network/SDN/Ipams/Plugin.pm @@ -15,22 +15,27 @@ 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', -}); + 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 undef if $noerr; + die "ipam ID '$id' contains illegal characters\n"; } return $id; } @@ -38,15 +43,19 @@ sub parse_sdn_ipam_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, - }), - fingerprint => get_standard_option('fingerprint-sha256', { optional => 1 }), + 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, + }, + ), + fingerprint => get_standard_option('fingerprint-sha256', { optional => 1 }), }, }; @@ -59,16 +68,15 @@ sub parse_section_header { 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); + 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) = @_; @@ -88,13 +96,35 @@ sub del_subnet { } sub add_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_; + my ( + $class, + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $vmid, + $is_gateway, + $noerr, + ) = @_; die "please implement inside plugin"; } sub update_ip { - my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_; + my ( + $class, + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $vmid, + $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 @@ -107,7 +137,6 @@ sub add_next_freeip { die "please implement inside plugin"; } - sub add_range_next_freeip { my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_; @@ -127,7 +156,7 @@ sub get_ips_from_mac { } sub on_update_hook { - my ($class, $plugin_config) = @_; + my ($class, $plugin_config) = @_; } 1; diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm index 8a79eae..b47379d 100644 --- a/src/PVE/Network/SDN/SubnetPlugin.pm +++ b/src/PVE/Network/SDN/SubnetPlugin.pm @@ -14,31 +14,39 @@ 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::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; + if ($id =~ /\//) { + $cidr = $id; } else { - my ($zone, $ip, $mask) = split(/-/, $id); - $cidr = "$ip/$mask"; + my ($zone, $ip, $mask) = split(/-/, $id); + $cidr = "$ip/$mask"; } - if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) || - PVE::JSONSchema::pve_verify_cidrv6($cidr, 1))) - { + 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"; } @@ -48,8 +56,10 @@ sub parse_sdn_subnet_id { my $defaultData = { propertyList => { - subnet => get_standard_option('pve-sdn-subnet-id', - { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }), + subnet => get_standard_option( + 'pve-sdn-subnet-id', + { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }, + ), }, }; @@ -63,12 +73,12 @@ sub private { my $dhcp_range_fmt = { 'start-address' => { - type => 'ip', - description => 'Start address for the DHCP IP range', + type => 'ip', + description => 'Start address for the DHCP IP range', }, 'end-address' => { - type => 'ip', - description => 'End address for the DHCP IP range', + type => 'ip', + description => 'End address for the DHCP IP range', }, }; @@ -85,34 +95,45 @@ sub validate_dhcp_ranges { my $validated_ranges = []; foreach my $dhcp_range (@$dhcp_ranges) { - my $dhcp_start = $dhcp_range->{'start-address'}; - my $dhcp_end = $dhcp_range->{'end-address'}; - - my $start_ip = Net::IP->new($dhcp_start); - raise_param_exc({ 'dhcp-range' => "start-address is not a valid IP $dhcp_start" }) if !$start_ip; - raise_param_exc({ 'dhcp-range' => "start-address must be a singular IP" }) if $start_ip->size() != 1; - $dhcp_range->{'start-address'} = $start_ip->ip(); - - my $end_ip = Net::IP->new($dhcp_end); - raise_param_exc({ 'dhcp-range' => "end-address is not a valid IP $dhcp_end" }) if !$end_ip; - raise_param_exc({ 'dhcp-range' => "end-address must be a singular IP" }) if $end_ip->size() != 1; - $dhcp_range->{'end-address'} = $end_ip->ip(); - - if ($start_ip->bincomp('gt', $end_ip)) { - raise_param_exc({ 'dhcp-range' => "start-address $dhcp_start must be smaller than end-address $dhcp_end" }) - } - - raise_param_exc({ 'dhcp-range' => "start-address $dhcp_start is not in subnet $cidr" }) if !$subnet_matcher->($dhcp_start); - raise_param_exc({ 'dhcp-range' => "end-address $dhcp_end is not in subnet $cidr" }) if !$subnet_matcher->($dhcp_end); - - my $ip_range = Net::IP->new("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}"); - for my $other_range (@$validated_ranges) { - if ($ip_range->overlaps($other_range) != $Net::IP::IP_NO_OVERLAP) { - raise_param_exc({ 'dhcp-range' => "dhcp ranges must not overlap" }); - } - } - - push @$validated_ranges, $ip_range; + my $dhcp_start = $dhcp_range->{'start-address'}; + my $dhcp_end = $dhcp_range->{'end-address'}; + + my $start_ip = Net::IP->new($dhcp_start); + raise_param_exc({ 'dhcp-range' => "start-address is not a valid IP $dhcp_start" }) + if !$start_ip; + raise_param_exc({ 'dhcp-range' => "start-address must be a singular IP" }) + if $start_ip->size() != 1; + $dhcp_range->{'start-address'} = $start_ip->ip(); + + my $end_ip = Net::IP->new($dhcp_end); + raise_param_exc({ 'dhcp-range' => "end-address is not a valid IP $dhcp_end" }) if !$end_ip; + raise_param_exc({ 'dhcp-range' => "end-address must be a singular IP" }) + if $end_ip->size() != 1; + $dhcp_range->{'end-address'} = $end_ip->ip(); + + if ($start_ip->bincomp('gt', $end_ip)) { + raise_param_exc( + { + 'dhcp-range' => + "start-address $dhcp_start must be smaller than end-address $dhcp_end", + }, + ); + } + + raise_param_exc({ 'dhcp-range' => "start-address $dhcp_start is not in subnet $cidr" }) + if !$subnet_matcher->($dhcp_start); + raise_param_exc({ 'dhcp-range' => "end-address $dhcp_end is not in subnet $cidr" }) + if !$subnet_matcher->($dhcp_end); + + my $ip_range = + Net::IP->new("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}"); + for my $other_range (@$validated_ranges) { + if ($ip_range->overlaps($other_range) != $Net::IP::IP_NO_OVERLAP) { + raise_param_exc({ 'dhcp-range' => "dhcp ranges must not overlap" }); + } + } + + push @$validated_ranges, $ip_range; } } @@ -123,48 +144,51 @@ sub properties { description => "associated vnet", }, gateway => { - type => 'string', format => 'ip', + 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=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]", -# }, + # #cloudinit, dhcp options + # routes => { + # type => 'string', + # description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]", + # }, dnszoneprefix => { - type => 'string', format => 'dns-name', + type => 'string', + format => 'dns-name', description => "dns domain zone prefix ex: 'adm' -> <hostname>.adm.mydomain.com", }, - 'dhcp-range' => { - type => 'array', - description => 'A list of DHCP ranges for this subnet', - optional => 1, - items => { - type => 'string', - format => 'pve-sdn-dhcp-range', - } - }, - 'dhcp-dns-server' => { - type => 'string', format => 'ip', - description => 'IP address for the DNS server', - optional => 1, - }, + 'dhcp-range' => { + type => 'array', + description => 'A list of DHCP ranges for this subnet', + optional => 1, + items => { + type => 'string', + format => 'pve-sdn-dhcp-range', + }, + }, + 'dhcp-dns-server' => { + type => 'string', + format => 'ip', + description => 'IP address for the DNS server', + optional => 1, + }, }; } sub options { return { - vnet => { optional => 0 }, - gateway => { optional => 1 }, -# routes => { optional => 1 }, - snat => { optional => 1 }, - dnszoneprefix => { optional => 1 }, - 'dhcp-range' => { optional => 1 }, - 'dhcp-dns-server' => { optional => 1 }, + vnet => { optional => 0 }, + gateway => { optional => 1 }, + # routes => { optional => 1 }, + snat => { optional => 1 }, + dnszoneprefix => { optional => 1 }, + 'dhcp-range' => { optional => 1 }, + 'dhcp-dns-server' => { optional => 1 }, }; } @@ -186,49 +210,53 @@ sub on_update_hook { 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}; + 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; + raise_param_exc({ gateway => "$gateway is not in subnet $cidr" }) + if $gateway && !$subnet_matcher->($gateway) && !$pointopoint; validate_dhcp_ranges($subnet); if ($ipam) { - if ($old_subnet) { - PVE::Network::SDN::Subnets::update_subnet($zone, $subnetid, $subnet, $old_subnet); - } else { - 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"; - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, undef, 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 $@; - } + if ($old_subnet) { + PVE::Network::SDN::Subnets::update_subnet($zone, $subnetid, $subnet, $old_subnet); + } else { + 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"; + PVE::Network::SDN::Subnets::add_ip( + $zone, $subnetid, $subnet, $gateway, $hostname, $mac, undef, 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 $@; + } } } diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm index 18847c2..1bcb47a 100644 --- a/src/PVE/Network/SDN/Subnets.pm +++ b/src/PVE/Network/SDN/Subnets.pm @@ -25,13 +25,13 @@ sub sdn_subnets_config { die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg); if ($scfg) { - $scfg->{id} = $id; + $scfg->{id} = $id; - my ($zone, $network, $mask) = split(/-/, $id); - $scfg->{cidr} = "$network/$mask"; - $scfg->{zone} = $zone; - $scfg->{network} = $network; - $scfg->{mask} = $mask; + my ($zone, $network, $mask) = split(/-/, $id); + $scfg->{cidr} = "$network/$mask"; + $scfg->{zone} = $zone; + $scfg->{network} = $network; + $scfg->{mask} = $mask; } return $scfg; @@ -43,17 +43,17 @@ sub get_dhcp_ranges { my @dhcp_ranges = (); if ($subnet_config->{'dhcp-range'}) { - foreach my $element (@{$subnet_config->{'dhcp-range'}}) { - my $dhcp_range = eval { parse_property_string('pve-sdn-dhcp-range', $element) }; + foreach my $element (@{ $subnet_config->{'dhcp-range'} }) { + my $dhcp_range = eval { parse_property_string('pve-sdn-dhcp-range', $element) }; - if ($@ || !$dhcp_range) { - warn "Unable to parse dhcp-range string: $element\n"; - warn "$@\n" if $@; - next; - } + if ($@ || !$dhcp_range) { + warn "Unable to parse dhcp-range string: $element\n"; + warn "$@\n" if $@; + next; + } - push @dhcp_ranges, $dhcp_range; - } + push @dhcp_ranges, $dhcp_range; + } } return \@dhcp_ranges; @@ -63,8 +63,8 @@ sub config { my ($running) = @_; if ($running) { - my $cfg = PVE::Network::SDN::running_config(); - return $cfg->{subnets}; + my $cfg = PVE::Network::SDN::running_config(); + return $cfg->{subnets}; } return cfs_read_file("sdn/subnets.cfg"); @@ -79,7 +79,7 @@ sub write_config { sub sdn_subnets_ids { my ($cfg) = @_; - return sort keys %{$cfg->{ids}}; + return sort keys %{ $cfg->{ids} }; } sub complete_sdn_subnet { @@ -87,7 +87,7 @@ sub complete_sdn_subnet { my $cfg = PVE::Network::SDN::Subnets::config(); - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ]; + return $cmdname eq 'add' ? [] : [PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg)]; } sub get_subnet { @@ -104,14 +104,14 @@ sub find_ip_subnet { my $subnetid = undef; foreach my $id (sort keys %{$subnets}) { - my $cidr = $subnets->{$id}->{cidr}; - my $subnet_matcher = subnet_matcher($cidr); - next if !$subnet_matcher->($ip); - $subnet = $subnets->{$id}; - $subnetid = $id; - last; + 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; + die "can't find any subnet for ip $ip" if !$subnet; return ($subnetid, $subnet); } @@ -234,52 +234,53 @@ sub add_next_free_ip { #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 { - if ($dhcprange) { - my $data = { - mac => $mac, - hostname => $hostname, - vmid => $vmid, - }; - - my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet); - - foreach my $range (@$dhcp_ranges) { - $ip = $plugin->add_range_next_freeip($plugin_config, $subnet, $range, $data); - last if $ip; - } - } else { - $ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid); - } - }; - - die $@ if $@; - - eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip); }; - warn $@ if $@; + 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 { + if ($dhcprange) { + my $data = { + mac => $mac, + hostname => $hostname, + vmid => $vmid, + }; + + my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet); + + foreach my $range (@$dhcp_ranges) { + $ip = + $plugin->add_range_next_freeip($plugin_config, $subnet, $range, $data); + last if $ip; + } + } else { + $ip = $plugin->add_next_freeip( + $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, + ); + } + }; + + die $@ if $@; + + eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip); }; + warn $@ 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); - } + 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, $mac) - }; - die $err; + #rollback + my $err = $@; + eval { PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac) }; + die $err; } return $ip; } @@ -287,7 +288,7 @@ sub add_next_free_ip { sub add_ip { my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $skipdns) = @_; - return if !$subnet || !$ip; + return if !$subnet || !$ip; my $ipaddr = NetAddr::IP->new($ip); $ip = $ipaddr->canon(); @@ -302,48 +303,48 @@ sub add_ip { $hostname .= ".$dnszoneprefix" if $dnszoneprefix; #verify dns zones before ipam - if(!$skipdns) { - verify_dns_zone($dnszone, $dns); - verify_dns_zone($reversednszone, $reversedns); + 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}); + 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, $vmid, $is_gateway); - }; - die $@ if $@; + eval { + $plugin->add_ip( + $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, + ); + }; + die $@ if $@; - eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip) if $mac; }; - warn $@ if $@; + eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip) if $mac; }; + warn $@ 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 (!$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, $mac) - }; - die $err; + #rollback + my $err = $@; + eval { PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac) }; + die $err; } } sub update_ip { my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns) = @_; - return if !$subnet || !$ip; + return if !$subnet || !$ip; my $ipaddr = NetAddr::IP->new($ip); $ip = $ipaddr->canon(); @@ -358,32 +359,32 @@ sub update_ip { $hostname .= ".$dnszoneprefix" if $dnszoneprefix; #verify dns zones before ipam - if(!$skipdns) { - verify_dns_zone($dnszone, $dns); - verify_dns_zone($reversednszone, $reversedns); + 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, $vmid); - }; - die $@ if $@; + 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, $vmid); + }; + 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); - } + 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); + } }; } @@ -403,31 +404,31 @@ sub del_ip { my $dnszoneprefix = $subnet->{dnszoneprefix}; $hostname .= ".$dnszoneprefix" if $dnszoneprefix; - if(!$skipdns) { - verify_dns_zone($dnszone, $dns); - verify_dns_zone($reversednszone, $reversedns); + 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); - - if ($mac) { - eval { PVE::Network::SDN::Ipams::del_cache_mac_ip($mac, $ip) }; - warn $@ if $@; - } + 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); + + if ($mac) { + eval { PVE::Network::SDN::Ipams::del_cache_mac_ip($mac, $ip) }; + warn $@ if $@; + } } eval { - if(!$skipdns) { - del_dns_record($dnszone, $dns, $hostname, $ip); - del_dns_ptr_record($reversednszone, $reversedns, $ip); - } + if (!$skipdns) { + del_dns_record($dnszone, $dns, $hostname, $ip); + del_dns_ptr_record($reversednszone, $reversedns, $ip); + } }; if ($@) { - warn $@; + warn $@; } } diff --git a/src/PVE/Network/SDN/VnetPlugin.pm b/src/PVE/Network/SDN/VnetPlugin.pm index f44380a..035aaca 100644 --- a/src/PVE/Network/SDN/VnetPlugin.pm +++ b/src/PVE/Network/SDN/VnetPlugin.pm @@ -10,16 +10,23 @@ 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::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) = @_; @@ -34,8 +41,10 @@ sub parse_sdn_vnet_id { my $defaultData = { propertyList => { - vnet => get_standard_option('pve-sdn-vnet-id', - { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }), + vnet => get_standard_option( + 'pve-sdn-vnet-id', + { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }, + ), }, }; @@ -49,43 +58,43 @@ sub private { sub properties { return { - zone => { + zone => { type => 'string', description => "zone id", - }, + }, type => { description => "Type", optional => 1, }, - tag => { + tag => { type => 'integer', description => "vlan or vxlan id", - }, - vlanaware => { - type => 'boolean', - description => 'Allow vm VLANs to pass through this vnet.', - }, + }, + 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, + optional => 1, + }, + 'isolate-ports' => { + type => 'boolean', + description => "If true, sets the isolated property for all members of this VNet", }, - 'isolate-ports' => { - type => 'boolean', - description => "If true, sets the isolated property for all members of this VNet", - } }; } sub options { return { - zone => { optional => 0}, - tag => { optional => 1}, + zone => { optional => 0 }, + tag => { optional => 1 }, alias => { optional => 1 }, vlanaware => { optional => 1 }, - 'isolate-ports' => { optional => 1 }, + 'isolate-ports' => { optional => 1 }, }; } @@ -94,7 +103,7 @@ sub on_delete_hook { #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; + raise_param_exc({ vnet => "Can't delete vnet if subnets exists" }) if $subnets; } sub on_update_hook { @@ -105,9 +114,10 @@ sub on_update_hook { 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; + 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; } } diff --git a/src/PVE/Network/SDN/Vnets.pm b/src/PVE/Network/SDN/Vnets.pm index 4e795f2..c16e7e5 100644 --- a/src/PVE/Network/SDN/Vnets.pm +++ b/src/PVE/Network/SDN/Vnets.pm @@ -30,8 +30,8 @@ sub config { my ($running) = @_; if ($running) { - my $cfg = PVE::Network::SDN::running_config(); - return $cfg->{vnets}; + my $cfg = PVE::Network::SDN::running_config(); + return $cfg->{vnets}; } return cfs_read_file("sdn/vnets.cfg"); @@ -46,7 +46,7 @@ sub write_config { sub sdn_vnets_ids { my ($cfg) = @_; - return sort keys %{$cfg->{ids}}; + return sort keys %{ $cfg->{ids} }; } sub complete_sdn_vnet { @@ -54,7 +54,7 @@ sub complete_sdn_vnet { my $cfg = PVE::Network::SDN::Vnets::config(); - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg) ]; + return $cmdname eq 'add' ? [] : [PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg)]; } sub get_vnet { @@ -72,10 +72,10 @@ sub get_subnets { my $subnets = undef; my $subnets_cfg = PVE::Network::SDN::Subnets::config($running); - foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) { - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); - next if !$subnet->{vnet} || ($vnetid && $subnet->{vnet} ne $vnetid); - $subnets->{$subnetid} = $subnet; + foreach my $subnetid (sort keys %{ $subnets_cfg->{ids} }) { + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); + next if !$subnet->{vnet} || ($vnetid && $subnet->{vnet} ne $vnetid); + $subnets->{$subnetid} = $subnet; } return $subnets; @@ -111,36 +111,47 @@ sub add_next_free_cidr { my @ipversions = defined($ipversion) ? ($ipversion) : qw/ 4 6 /; for my $ipversion (@ipversions) { - my $ip = undef; - my $subnetcount = 0; - foreach my $subnetid (sort keys %{$subnets}) { - my $subnet = $subnets->{$subnetid}; - my $network = $subnet->{network}; - - next if Net::IP::ip_get_version($network) != $ipversion || $ips->{$ipversion}; - $subnetcount++; - - eval { - $ip = PVE::Network::SDN::Subnets::add_next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $vmid, $skipdns, $subnet->{'dhcp-range'}); - }; - die $@ if $@; - - if ($ip) { - $ips->{$ipversion} = $ip; - last; - } - } - - if (!$ip && $subnetcount > 0) { - foreach my $version (sort keys %{$ips}) { - my $ip = $ips->{$version}; - my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $subnets); - - PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns); - } - - die "can't find any free ip in zone $zoneid for IPv$ipversion"; - } + my $ip = undef; + my $subnetcount = 0; + foreach my $subnetid (sort keys %{$subnets}) { + my $subnet = $subnets->{$subnetid}; + my $network = $subnet->{network}; + + next if Net::IP::ip_get_version($network) != $ipversion || $ips->{$ipversion}; + $subnetcount++; + + eval { + $ip = PVE::Network::SDN::Subnets::add_next_free_ip( + $zone, + $subnetid, + $subnet, + $hostname, + $mac, + $vmid, + $skipdns, + $subnet->{'dhcp-range'}, + ); + }; + die $@ if $@; + + if ($ip) { + $ips->{$ipversion} = $ip; + last; + } + } + + if (!$ip && $subnetcount > 0) { + foreach my $version (sort keys %{$ips}) { + my $ip = $ips->{$version}; + my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $subnets); + + PVE::Network::SDN::Subnets::del_ip( + $zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns, + ); + } + + die "can't find any free ip in zone $zoneid for IPv$ipversion"; + } } } @@ -148,9 +159,12 @@ sub add_ip { my ($vnetid, $ip, $hostname, $mac, $vmid, $skipdns) = @_; return if !$vnetid; - - my ($zone, $subnetid, $subnet) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip); - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, undef, $skipdns); + + my ($zone, $subnetid, $subnet) = + PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip); + PVE::Network::SDN::Subnets::add_ip( + $zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, undef, $skipdns, + ); } sub update_ip { @@ -158,8 +172,11 @@ sub update_ip { return if !$vnetid; - my ($zone, $subnetid, $subnet) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip); - PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns); + my ($zone, $subnetid, $subnet) = + PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip); + PVE::Network::SDN::Subnets::update_ip( + $zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns, + ); } sub del_ip { @@ -167,7 +184,8 @@ sub del_ip { return if !$vnetid; - my ($zone, $subnetid, $subnet) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip); + my ($zone, $subnetid, $subnet) = + PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip); PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns); } @@ -206,8 +224,8 @@ sub add_dhcp_mapping { return if !$zone->{ipam} || !$zone->{dhcp}; my ($ip4, $ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnetid, $mac); - add_next_free_cidr($vnetid, $name, $mac, "$vmid", undef, 1, 4) if ! $ip4; - add_next_free_cidr($vnetid, $name, $mac, "$vmid", undef, 1, 6) if ! $ip6; + add_next_free_cidr($vnetid, $name, $mac, "$vmid", undef, 1, 4) if !$ip4; + add_next_free_cidr($vnetid, $name, $mac, "$vmid", undef, 1, 6) if !$ip6; ($ip4, $ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnetid, $mac); PVE::Network::SDN::Dhcp::add_mapping($vnetid, $mac, $ip4, $ip6) if $ip4 || $ip6; diff --git a/src/PVE/Network/SDN/Zones.pm b/src/PVE/Network/SDN/Zones.pm index c1c7745..01c168c 100644 --- a/src/PVE/Network/SDN/Zones.pm +++ b/src/PVE/Network/SDN/Zones.pm @@ -44,8 +44,8 @@ sub config { my ($running) = @_; if ($running) { - my $cfg = PVE::Network::SDN::running_config(); - return $cfg->{zones}; + my $cfg = PVE::Network::SDN::running_config(); + return $cfg->{zones}; } return cfs_read_file("sdn/zones.cfg"); @@ -67,7 +67,7 @@ sub write_config { sub sdn_zones_ids { my ($cfg) = @_; - return sort keys %{$cfg->{ids}}; + return sort keys %{ $cfg->{ids} }; } sub complete_sdn_zone { @@ -75,7 +75,7 @@ sub complete_sdn_zone { my $cfg = PVE::Network::SDN::running_config(); - return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ]; + return $cmdname eq 'add' ? [] : [PVE::Network::SDN::sdn_zones_ids($cfg)]; } sub get_zone { @@ -96,7 +96,7 @@ sub get_vnets { my $vnets_config = PVE::Network::SDN::Vnets::config($running); my $vnets = undef; - for my $vnetid (keys %{$vnets_config->{ids}}) { + for my $vnetid (keys %{ $vnets_config->{ids} }) { my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_config, $vnetid); next if !$vnet->{zone} || $vnet->{zone} ne $zoneid; $vnets->{$vnetid} = $vnet; @@ -122,47 +122,57 @@ sub generate_etc_network_config { 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; - } + 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"; - } + $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; @@ -173,7 +183,7 @@ sub write_etc_network_config { return if !$rawconfig; - my $writefh = IO::File->new($local_network_sdn_file,">"); + my $writefh = IO::File->new($local_network_sdn_file, ">"); print $writefh $rawconfig; $writefh->close(); } @@ -184,31 +194,29 @@ sub read_etc_network_config_version { return if !defined($versionstr); if ($versionstr =~ m/^\#version:(\d+)$/) { - return $1; + return $1; } } sub ifquery_check { - my $cmd = ['ifquery', '-a', '-c', '-o','json']; + my $cmd = ['ifquery', '-a', '-c', '-o', 'json']; my $result = ''; my $reader = sub { $result .= shift }; - eval { - run_command($cmd, outfunc => $reader); - }; + 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}, - }; + my $name = $interface->{name}; + $interfaces->{$name} = { + status => $interface->{status}, + config => $interface->{config}, + config_status => $interface->{config_status}, + }; } return $interfaces; @@ -227,19 +235,19 @@ sub status { 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"; - } + $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"; - } + $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; + $warned_about_reload = 0; } my $status = ifquery_check(); @@ -251,42 +259,44 @@ sub status { 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'; + 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->{$zone}->{status} = 'error'; - } + 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->{$zone}->{status} = 'error'; + } } return ($zone_status, $vnet_status); @@ -297,8 +307,8 @@ sub tap_create { my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); if (!$vnet) { # fallback for classic bridge - PVE::Network::tap_create($iface, $bridge); - return; + PVE::Network::tap_create($iface, $bridge); + return; } my $plugin_config = get_plugin_config($vnet); @@ -311,8 +321,8 @@ sub veth_create { 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; + PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr); + return; } my $plugin_config = get_plugin_config($vnet); @@ -325,18 +335,20 @@ sub tap_plug { 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 $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}); + 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); @@ -347,8 +359,8 @@ sub add_bridge_fdb { my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); if (!$vnet) { # fallback for classic bridge - PVE::Network::add_bridge_fdb($iface, $macaddr); - return; + PVE::Network::add_bridge_fdb($iface, $macaddr); + return; } my $plugin_config = get_plugin_config($vnet); @@ -361,8 +373,8 @@ sub del_bridge_fdb { my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1); if (!$vnet) { # fallback for classic bridge - PVE::Network::del_bridge_fdb($iface, $macaddr); - return; + PVE::Network::del_bridge_fdb($iface, $macaddr); + return; } my $plugin_config = get_plugin_config($vnet); diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm index 4843756..7d26e1b 100644 --- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm @@ -21,91 +21,107 @@ sub type { } 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"; - } + 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"; + 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' - } + '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 }, + 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 }, 'bridge-disable-mac-learning' => { optional => 1 }, - 'rt-import' => { optional => 1 }, - 'vxlan-port' => { optional => 1 }, - mtu => { optional => 1 }, - mac => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, + 'rt-import' => { optional => 1 }, + 'vxlan-port' => { 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 ( + $class, + $plugin_config, + $zoneid, + $vnetid, + $vnet, + $controller, + $controller_cfg, + $subnet_cfg, + $interfaces_config, + $config, + ) = @_; my $tag = $vnet->{tag}; my $alias = $vnet->{alias}; @@ -122,19 +138,23 @@ sub generate_sdn_config { my @peers = PVE::Tools::split_list($controller->{'peers'}); my $loopback = undef; - my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg); - my $isisrouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_isis_controller($local_node, $controller_cfg); + my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller( + $local_node, $controller_cfg, + ); + my $isisrouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_isis_controller( + $local_node, $controller_cfg, + ); if ($bgprouter->{loopback}) { - $loopback = $bgprouter->{loopback}; + $loopback = $bgprouter->{loopback}; } elsif ($isisrouter->{loopback}) { - $loopback = $isisrouter->{loopback}; + $loopback = $isisrouter->{loopback}; } - my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $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}; @@ -146,10 +166,11 @@ sub generate_sdn_config { push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip; push @iface_config, "vxlan-port $vxlanport" if $vxlanport; push @iface_config, "bridge-learning off"; - push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'}; + 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}; + push(@{ $config->{$vxlan_iface} }, @iface_config) if !$config->{$vxlan_iface}; #vnet bridge @iface_config = (); @@ -161,43 +182,48 @@ sub generate_sdn_config { 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 $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 ($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}) { + if ($subnet->{snat}) { #find outgoing interface - my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip); + 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"; + 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, + "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"; } } } @@ -210,65 +236,68 @@ sub generate_sdn_config { 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, "arp-accept on" if $ipv4 || $ipv6; push @iface_config, "vrf $vrf_iface" if $vrf_iface; - push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid}; + 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, "vxlan-port $vxlanport" if $vxlanport; - 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}; - } + #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, "vxlan-port $vxlanport" if $vxlanport; + 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; } @@ -279,50 +308,53 @@ sub on_update_hook { # verify that controller exist my $controller = $zone_cfg->{ids}->{$zoneid}->{controller}; if (!defined($controller_cfg->{ids}->{$controller})) { - die "controller $controller don't exist"; + die "controller $controller don't exist"; } else { - die "$controller is not a evpn controller type" if $controller_cfg->{ids}->{$controller}->{type} ne 'evpn'; + 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); + 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::Network::SDN::Zones::Plugin->datacenter_config(); - $zone_cfg->{ids}->{$zoneid}->{'mac'} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); + $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; - raise_param_exc({ 'vlan-aware' => "vlan-aware option can't be enabled with evpn"}) if $vnet->{vlanaware}; + raise_param_exc({ tag => "missing vxlan tag" }) if !defined($tag); + raise_param_exc({ tag => "vxlan tag max value is 16777216" }) if $tag > 16777216; + raise_param_exc({ 'vlan-aware' => "vlan-aware option can't be enabled with evpn" }) + if $vnet->{vlanaware}; # 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; + 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 index a237d17..5f069ae 100644 --- a/src/PVE/Network/SDN/Zones/FaucetPlugin.pm +++ b/src/PVE/Network/SDN/Zones/FaucetPlugin.pm @@ -22,14 +22,14 @@ sub properties { 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 }, + 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 }, }; } @@ -40,7 +40,7 @@ sub generate_sdn_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 $dphex = printf("%x", $dpid); #fixme :should be 16characters hex my $iface = $uplinks->{$uplink}->{name}; $iface = "uplink${uplink}" if !$iface; @@ -50,7 +50,7 @@ sub generate_sdn_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}; + push(@{ $config->{$iface} }, @iface_config) if !$config->{$iface}; #vnet bridge @iface_config = (); @@ -63,12 +63,10 @@ sub generate_sdn_config { 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}; + push(@{ $config->{$zoneid} }, @iface_config) if !$config->{$zoneid}; return $config; } - 1; - diff --git a/src/PVE/Network/SDN/Zones/Plugin.pm b/src/PVE/Network/SDN/Zones/Plugin.pm index a860168..5f49357 100644 --- a/src/PVE/Network/SDN/Zones/Plugin.pm +++ b/src/PVE/Network/SDN/Zones/Plugin.pm @@ -17,18 +17,23 @@ PVE::Cluster::cfs_register_file( 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_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"; + 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; @@ -37,20 +42,24 @@ sub parse_sdn_zone_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, - }, + 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, + }, }, }; @@ -63,11 +72,11 @@ sub parse_section_header { 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); + 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; } @@ -76,15 +85,15 @@ sub decode_value { my ($class, $type, $key, $value) = @_; if ($key eq 'nodes' || $key eq 'exitnodes') { - my $res = {}; + my $res = {}; - foreach my $node (PVE::Tools::split_list($value)) { - if (PVE::JSONSchema::pve_verify_node_name($node)) { - $res->{$node} = 1; - } - } + foreach my $node (PVE::Tools::split_list($value)) { + if (PVE::JSONSchema::pve_verify_node_name($node)) { + $res->{$node} = 1; + } + } - return $res; + return $res; } return $value; @@ -94,14 +103,25 @@ sub encode_value { my ($class, $type, $key, $value) = @_; if ($key eq 'nodes' || $key eq 'exitnodes') { - return join(',', keys(%$value)); + 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) = @_; + my ( + $class, + $plugin_config, + $zoneid, + $vnetid, + $vnet, + $controller, + $controller_cfg, + $subnet_cfg, + $interfaces_config, + $config, + ) = @_; die "please implement inside plugin"; } @@ -133,10 +153,10 @@ 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); + 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); } } @@ -162,28 +182,29 @@ sub parse_tag_number_or_range { 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; - } - } + 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; @@ -196,13 +217,13 @@ sub generate_status_message { my $err_msg = []; return ["vnet is not generated. Please check the 'reload network' task log."] - if !$status->{$vnetid}->{status}; + if !$status->{$vnetid}->{status}; foreach my $iface (@{$ifaces}) { if (!$status->{$iface}->{status}) { - push @$err_msg, "missing $iface"; + push @$err_msg, "missing $iface"; } elsif ($status->{$iface}->{status} ne 'pass') { - push @$err_msg, "error $iface"; + push @$err_msg, "error $iface"; } } @@ -215,7 +236,6 @@ sub status { return $class->generate_status_message($vnetid, $status); } - sub tap_create { my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_; @@ -231,7 +251,8 @@ sub veth_create { 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"); + 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 = {}; @@ -243,13 +264,15 @@ sub tap_plug { sub add_bridge_fdb { my ($class, $plugin_config, $iface, $macaddr) = @_; - PVE::Network::add_bridge_fdb($iface, $macaddr) if $plugin_config->{'bridge-disable-mac-learning'}; + PVE::Network::add_bridge_fdb($iface, $macaddr) + if $plugin_config->{'bridge-disable-mac-learning'}; } sub del_bridge_fdb { my ($class, $plugin_config, $iface, $macaddr) = @_; - PVE::Network::del_bridge_fdb($iface, $macaddr) if $plugin_config->{'bridge-disable-mac-learning'}; + PVE::Network::del_bridge_fdb($iface, $macaddr) + if $plugin_config->{'bridge-disable-mac-learning'}; } #helper @@ -258,20 +281,20 @@ sub get_uplink_iface { my ($interfaces_config, $uplink) = @_; my $iface = undef; - foreach my $id (keys %{$interfaces_config->{ifaces}}) { + 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') { + 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; + $iface = $id; } } #create a dummy uplink interface if no uplink found - if(!$iface) { + if (!$iface) { warn "can't find uplink $uplink in physical interface"; $iface = "uplink${uplink}"; } @@ -285,45 +308,47 @@ sub get_local_route_ip { 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; - } + 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; + 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); - } - } + 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); + my ($ip, $interface) = get_local_route_ip($address); + return ($ip, $interface); } } @@ -343,7 +368,7 @@ sub is_ovs { my ($bridge) = @_; my $is_ovs = !-d "/sys/class/net/$bridge/brif"; - return $is_ovs; + return $is_ovs; } sub get_bridge_ifaces { @@ -351,9 +376,13 @@ sub get_bridge_ifaces { 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]; - }); + PVE::Tools::dir_glob_foreach( + $dir, + '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', + sub { + push @bridge_ifaces, $_[0]; + }, + ); return @bridge_ifaces; } @@ -362,7 +391,6 @@ sub datacenter_config { return PVE::Cluster::cfs_read_file('datacenter.cfg'); } - sub get_mtu { my ($class, $plugin_config) = @_; diff --git a/src/PVE/Network/SDN/Zones/QinQPlugin.pm b/src/PVE/Network/SDN/Zones/QinQPlugin.pm index 4c4be64..606c9f9 100644 --- a/src/PVE/Network/SDN/Zones/QinQPlugin.pm +++ b/src/PVE/Network/SDN/Zones/QinQPlugin.pm @@ -15,45 +15,56 @@ sub type { 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, - } + 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 }, + 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 }, + '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 ( + $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); @@ -68,79 +79,82 @@ sub generate_sdn_config { my $vnet_bridge_ports = ""; if (my $ctag = $vnet->{tag}) { - $vnet_bridge_ports = "$zone.$ctag"; + $vnet_bridge_ports = "$zone.$ctag"; } else { - $vnet_bridge_ports = $zone_notag_uplinkpeer; + $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; + $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}; + # 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 ); + @{ $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; + $zone_bridge_ports = $svlan_iface; } elsif ($vlan_aware) { # VLAN_aware_brige-(tag)----->vlanwarebridge-(tag)----->vnet - $zone_bridge_ports = "$bridge.$stag"; + $zone_bridge_ports = "$bridge.$stag"; - if ($vlanprotocol) { - @iface_config = (); - push @iface_config, "bridge-vlan-protocol $vlanprotocol"; - push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge}; + if ($vlanprotocol) { + @iface_config = (); + push @iface_config, "bridge-vlan-protocol $vlanprotocol"; + push(@{ $config->{$bridge} }, @iface_config) if !$config->{$bridge}; - @iface_config = (); - push @iface_config, "vlan-protocol $vlanprotocol"; - push(@{$config->{$zone_bridge_ports}}, @iface_config) if !$config->{$zone_bridge_ports}; - } + @iface_config = (); + push @iface_config, "vlan-protocol $vlanprotocol"; + push(@{ $config->{$zone_bridge_ports} }, @iface_config) + if !$config->{$zone_bridge_ports}; + } } else { - # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet + # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet - my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge); + 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"; + 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}; + # 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; + $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}; + 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}; + push(@{ $config->{$zone_notag_uplinkpeer} }, @iface_config) + if !$config->{$zone_notag_uplinkpeer}; # zone vlan aware bridge @iface_config = (); @@ -150,20 +164,20 @@ sub generate_sdn_config { 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}; + 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"; + 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}; + push(@{ $config->{$vnetid} }, @iface_config) if !$config->{$vnetid}; } sub status { @@ -172,28 +186,28 @@ sub status { my $bridge = $plugin_config->{bridge}; if (!-d "/sys/class/net/$bridge") { - return ["missing $bridge"]; + return ["missing $bridge"]; } 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 $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 ]; + 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; + my $svlan_iface = "sv_$zone"; + push @$ifaces, $svlan_iface; } return $class->generate_status_message($vnetid, $status, $ifaces); @@ -208,21 +222,21 @@ sub vnet_update_hook { 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; - } + 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 index 1416d39..97cf29e 100644 --- a/src/PVE/Network/SDN/Zones/SimplePlugin.pm +++ b/src/PVE/Network/SDN/Zones/SimplePlugin.pm @@ -16,43 +16,55 @@ sub type { 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", - }, - dhcp => { - description => 'Type of the DHCP backend for this zone', - type => 'string', - enum => PVE::Network::SDN::Dhcp::Plugin->lookup_types(), - }, + 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", + }, + dhcp => { + description => 'Type of the DHCP backend for this zone', + type => 'string', + enum => PVE::Network::SDN::Dhcp::Plugin->lookup_types(), + }, }; } sub options { return { - nodes => { optional => 1}, - mtu => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, - dhcp => { optional => 1 }, + nodes => { optional => 1 }, + mtu => { optional => 1 }, + dns => { optional => 1 }, + reversedns => { optional => 1 }, + dnszone => { optional => 1 }, + ipam => { optional => 1 }, + dhcp => { 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 ( + $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}; @@ -70,46 +82,51 @@ sub generate_sdn_config { 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"; - } - } + 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; @@ -125,7 +142,7 @@ sub generate_sdn_config { 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; + push @{ $config->{$vnetid} }, @iface_config; return $config; } @@ -136,7 +153,7 @@ sub vnet_update_hook { 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); + raise_param_exc({ tag => "vlan tag is not allowed on simple zone" }) if defined($tag); if (!defined($vnet->{mac})) { my $dc = PVE::Network::SDN::Zones::Plugin::datacenter_config(); @@ -152,4 +169,3 @@ sub get_mtu { 1; - diff --git a/src/PVE/Network/SDN/Zones/VlanPlugin.pm b/src/PVE/Network/SDN/Zones/VlanPlugin.pm index 13fb49e..155042e 100644 --- a/src/PVE/Network/SDN/Zones/VlanPlugin.pm +++ b/src/PVE/Network/SDN/Zones/VlanPlugin.pm @@ -12,43 +12,55 @@ sub type { } PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange); + sub pve_verify_sdn_vlanrange { - my ($vlanstr) = @_; + my ($vlanstr) = @_; - PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096'); + PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096'); - return $vlanstr; + return $vlanstr; } sub properties { return { - 'bridge' => { - type => 'string', - }, - 'bridge-disable-mac-learning' => { - type => 'boolean', + '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 }, + 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 ( + $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); @@ -60,67 +72,68 @@ sub generate_sdn_config { my $alias = $vnet->{alias}; my $mtu = $plugin_config->{mtu}; - my $vnet_uplink = "ln_".$vnetid; - my $vnet_uplinkpeer = "pr_".$vnetid; + my $vnet_uplink = "ln_" . $vnetid; + my $vnet_uplinkpeer = "pr_" . $vnetid; my @iface_config = (); - if($is_ovs) { + 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); + @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"; + $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 @iface_config, "mtu $mtu" if $mtu; - 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 @iface_config, "mtu $mtu" if $mtu; - 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 @iface_config, "mtu $mtu" if $mtu; - push(@{$config->{$bridgevlan}}, @iface_config) if !$config->{$bridgevlan}; + 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 @iface_config, "mtu $mtu" if $mtu; + 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 @iface_config, "mtu $mtu" if $mtu; + 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 @iface_config, "mtu $mtu" if $mtu; + push(@{ $config->{$bridgevlan} }, @iface_config) if !$config->{$bridgevlan}; } #vnet bridge @@ -128,13 +141,13 @@ sub generate_sdn_config { push @iface_config, "bridge_ports $vnet_uplink"; push @iface_config, "bridge_stp off"; push @iface_config, "bridge_fd 0"; - if($vnet->{vlanaware}) { + 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}; + push(@{ $config->{$vnetid} }, @iface_config) if !$config->{$vnetid}; return $config; } @@ -145,25 +158,25 @@ sub status { my $bridge = $plugin_config->{bridge}; if (!-d "/sys/class/net/$bridge") { - return ["missing $bridge"]; + return ["missing $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 $vnet_uplink = "ln_".$vnetid; - my $vnet_uplinkpeer = "pr_".$vnetid; + my $vnet_uplink = "ln_" . $vnetid; + my $vnet_uplinkpeer = "pr_" . $vnetid; # ifaces to check - my $ifaces = [ $vnetid, $bridge ]; - if($is_ovs) { - push @$ifaces, $vnet_uplink; + 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; + my $bridgevlan = $bridge . "v" . $tag; + push @$ifaces, $bridgevlan; + push @$ifaces, $vnet_uplink; + push @$ifaces, $vnet_uplinkpeer; } return $class->generate_status_message($vnetid, $status, $ifaces); @@ -175,19 +188,19 @@ sub vnet_update_hook { 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; + 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; + 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 index 9a77bb9..018d412 100644 --- a/src/PVE/Network/SDN/Zones/VxlanPlugin.pm +++ b/src/PVE/Network/SDN/Zones/VxlanPlugin.pm @@ -11,12 +11,13 @@ 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) = @_; + my ($vxlanstr) = @_; - PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216'); + PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216'); - return $vxlanstr; + return $vxlanstr; } sub type { @@ -27,33 +28,45 @@ sub properties { return { 'peers' => { description => "peers address list.", - type => 'string', format => 'ip-list' + type => 'string', + format => 'ip-list', }, 'vxlan-port' => { description => "Vxlan tunnel udp port (default 4789).", minimum => 1, maximum => 65536, - type => 'integer' + type => 'integer', }, }; } sub options { return { - nodes => { optional => 1}, - peers => { optional => 0 }, - 'vxlan-port' => { optional => 1 }, - mtu => { optional => 1 }, - dns => { optional => 1 }, - reversedns => { optional => 1 }, - dnszone => { optional => 1 }, - ipam => { optional => 1 }, + nodes => { optional => 1 }, + peers => { optional => 0 }, + 'vxlan-port' => { 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 ( + $class, + $plugin_config, + $zoneid, + $vnetid, + $vnet, + $controller, + $controller_cfg, + $subnet_cfg, + $interfaces_config, + $config, + ) = @_; my $tag = $vnet->{tag}; my $alias = $vnet->{alias}; @@ -65,7 +78,8 @@ sub generate_sdn_config { die "missing vxlan tag" if !$tag; - my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers); + 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}; @@ -76,14 +90,13 @@ sub generate_sdn_config { push @iface_config, "vxlan-id $tag"; for my $address (@peers) { - next if $address eq $ifaceip; - push @iface_config, "vxlan_remoteip $address"; + next if $address eq $ifaceip; + push @iface_config, "vxlan_remoteip $address"; } push @iface_config, "vxlan-port $vxlanport" if $vxlanport; - push @iface_config, "mtu $mtu" if $mtu; - push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface}; + push(@{ $config->{$vxlan_iface} }, @iface_config) if !$config->{$vxlan_iface}; #vnet bridge @iface_config = (); @@ -91,12 +104,12 @@ sub generate_sdn_config { 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, "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}; + push(@{ $config->{$vnetid} }, @iface_config) if !$config->{$vnetid}; return $config; } @@ -107,21 +120,22 @@ sub vnet_update_hook { 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; + 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; + 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/run_test_dns.pl b/src/test/run_test_dns.pl index 43280dc..26fbfaf 100755 --- a/src/test/run_test_dns.pl +++ b/src/test/run_test_dns.pl @@ -25,8 +25,8 @@ sub read_sdn_config { open my $in, '<', $file or die $!; my $sdn_config; { - local $/; # slurp mode - $sdn_config = eval <$in>; + local $/; # slurp mode + $sdn_config = eval <$in>; } close $in; @@ -43,27 +43,27 @@ foreach my $path (@plugins) { 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; - }, + 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); - }, + 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(); @@ -77,183 +77,183 @@ foreach my $path (@plugins) { 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, - }; - - 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, - }; - - my $record2 = { - content => $ip2, - disabled => JSON::false, - name => $fqdn, - type => $type, - }; - - 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, - }; - - 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); - } + 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, + }; + + 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, + }; + + my $record2 = { + content => $ip2, + disabled => JSON::false, + name => $fqdn, + type => $type, + }; + + 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, + }; + + 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 @@ -264,9 +264,9 @@ foreach my $path (@plugins) { $plugin->verify_zone($plugin_config, $zone, 1); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + fail($name); } } diff --git a/src/test/run_test_ipams.pl b/src/test/run_test_ipams.pl index 2196079..193b34b 100755 --- a/src/test/run_test_ipams.pl +++ b/src/test/run_test_ipams.pl @@ -25,8 +25,8 @@ sub read_sdn_config { open my $in, '<', $file or die $!; my $sdn_config; { - local $/; # slurp mode - $sdn_config = eval <$in>; + local $/; # slurp mode + $sdn_config = eval <$in>; } close $in; @@ -44,35 +44,35 @@ foreach my $path (@plugins) { my $pve_sdn_subnets; $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); $pve_sdn_subnets->mock( - config => sub { - return $sdn_config->{subnets}; - }, + 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; - }, + 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); - }, + 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; @@ -84,22 +84,22 @@ foreach my $path (@plugins) { my $is_gateway = 1; my $subnet = - PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); + 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; - }, + get_prefix_id => sub { + return 1; + }, + get_ip_id => sub { + return 1; + }, + is_ip_gateway => sub { + return 1; + }, ); ## add_ip @@ -107,13 +107,22 @@ foreach my $path (@plugins) { 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); + $plugin->add_ip( + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $description, + $is_gateway, + 1, + ); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + fail($name); } ## add_next_freeip @@ -124,9 +133,9 @@ foreach my $path (@plugins) { $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description, 1); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + fail($name); } ## del_ip @@ -137,22 +146,31 @@ foreach my $path (@plugins) { $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip, 1); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + 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); + $plugin->update_ip( + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $description, + $is_gateway, + 1, + ); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + fail($name); } ## add_ip_notgateway @@ -161,19 +179,28 @@ foreach my $path (@plugins) { $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); + $plugin->add_ip( + $plugin_config, + $subnetid, + $subnet, + $ip, + $hostname, + $mac, + $description, + $is_gateway, + 1, + ); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + fail($name); } $sdn_ipam_plugin->mock( - get_prefix_id => sub { - return undef; - }, + get_prefix_id => sub { + return undef; + }, ); ## add_subnet @@ -184,9 +211,9 @@ foreach my $path (@plugins) { $plugin->add_subnet($plugin_config, $subnetid, $subnet, 1); if ($@) { - is($@, $expected, $name); + is($@, $expected, $name); } else { - fail($name); + fail($name); } } diff --git a/src/test/run_test_subnets.pl b/src/test/run_test_subnets.pl index 79186c2..d4f8d66 100755 --- a/src/test/run_test_subnets.pl +++ b/src/test/run_test_subnets.pl @@ -41,8 +41,8 @@ sub read_sdn_config { open my $in, '<', $file or die $!; my $sdn_config; { - local $/; # slurp mode - $sdn_config = eval <$in>; + local $/; # slurp mode + $sdn_config = eval <$in>; } close $in; @@ -61,15 +61,15 @@ foreach my $path (@plugins) { 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; - }, + config => sub { + return $sdn_config->{subnets}; + }, + verify_dns_zone => sub { + return; + }, + add_dns_record => sub { + return; + }, ); my $js = JSON->new; @@ -79,7 +79,7 @@ foreach my $path (@plugins) { 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); + PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1); my $subnet_cidr = $subnet->{cidr}; my $iplist = NetAddr::IP->new($subnet_cidr); @@ -103,33 +103,33 @@ foreach my $path (@plugins) { 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; - }, - cfs_lock_file => $mocked_cfs_lock_file, - ); + $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; + }, + cfs_lock_file => $mocked_cfs_lock_file, + ); } 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_cache_mac_ip => sub { - return; - }, - del_cache_mac_ip => sub { - return; - }, + config => sub { + my $ipam_config = read_sdn_config("$path/ipam_config"); + return $ipam_config; + }, + add_cache_mac_ip => sub { + return; + }, + del_cache_mac_ip => sub { + return; + }, ); ## add_subnet @@ -139,17 +139,17 @@ foreach my $path (@plugins) { my $expected = '{"zones":{"myzone":{"subnets":{"' . $subnet_cidr . '":{"ips":{}}}}}}'; eval { - PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet); + PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet); }; if ($@) { - fail("$name : $@"); + fail("$name : $@"); } elsif ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); } else { - is(undef, undef, $name); + is(undef, undef, $name); } ## add_ip @@ -157,41 +157,43 @@ foreach my $path (@plugins) { $name = "$testid $test"; $result = undef; $expected = - '{"zones":{"myzone":{"subnets":{"' - . $subnet_cidr - . '":{"ips":{"' - . $ip - . '":{"gateway":1}}}}}}}'; + '{"zones":{"myzone":{"subnets":{"' + . $subnet_cidr + . '":{"ips":{"' + . $ip + . '":{"gateway":1}}}}}}}'; eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, - $is_gateway); + PVE::Network::SDN::Subnets::add_ip( + $zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, + ); }; if ($@) { - fail("$name : $@"); + fail("$name : $@"); } elsif ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); } else { - is(undef, undef, $name); + 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, - $vmid); - }; - - if ($@) { - is(undef, undef, $name); - } else { - fail("$name : $@"); - } + ## 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, $vmid, + ); + }; + + if ($@) { + is(undef, undef, $name); + } else { + fail("$name : $@"); + } } ## add_second_ip @@ -199,31 +201,33 @@ foreach my $path (@plugins) { $name = "$testid $test"; $result = undef; $expected = - '{"zones":{"myzone":{"subnets":{"' - . $subnet_cidr - . '":{"ips":{"' - . $ip - . '":{"gateway":1},"' - . $ip2 - . '":{"hostname":"' - . $hostname - . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"}}}}}}}'; + '{"zones":{"myzone":{"subnets":{"' + . $subnet_cidr + . '":{"ips":{"' + . $ip + . '":{"gateway":1},"' + . $ip2 + . '":{"hostname":"' + . $hostname + . '","mac":"' + . $mac + . '","vmid":"' + . $vmid + . '"}}}}}}}'; eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip2, $hostname, $mac, $vmid); + PVE::Network::SDN::Subnets::add_ip( + $zone, $subnetid, $subnet, $ip2, $hostname, $mac, $vmid, + ); }; if ($@) { - fail("$name : $@"); + fail("$name : $@"); } elsif ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); } else { - is(undef, undef, $name); + is(undef, undef, $name); } ## add_next_free @@ -231,38 +235,38 @@ foreach my $path (@plugins) { $name = "$testid $test"; $result = undef; $expected = - '{"zones":{"myzone":{"subnets":{"' - . $subnet_cidr - . '":{"ips":{"' - . $ip - . '":{"gateway":1},"' - . $ipnextfree - . '":{"hostname":"' + '{"zones":{"myzone":{"subnets":{"' + . $subnet_cidr + . '":{"ips":{"' + . $ip + . '":{"gateway":1},"' + . $ipnextfree + . '":{"hostname":"' + . $hostname + . '","mac":"' + . $mac + . '","vmid":"' + . $vmid . '"},"' + . $ip2 + . '":{"hostname":"' . $hostname . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"},"' - . $ip2 - . '":{"hostname":"' - . $hostname - . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"}}}}}}}'; + . $mac + . '","vmid":"' + . $vmid + . '"}}}}}}}'; eval { - $ip3 = PVE::Network::SDN::Subnets::add_next_free_ip($zone, $subnetid, $subnet, $hostname, - $mac, $vmid); + $ip3 = PVE::Network::SDN::Subnets::add_next_free_ip( + $zone, $subnetid, $subnet, $hostname, $mac, $vmid, + ); }; if ($@) { - fail("$name : $@"); + fail("$name : $@"); } elsif ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); } ## del_ip @@ -270,105 +274,105 @@ foreach my $path (@plugins) { $name = "$testid $test"; $result = undef; $expected = - '{"zones":{"myzone":{"subnets":{"' - . $subnet_cidr - . '":{"ips":{"' - . $ipnextfree - . '":{"hostname":"' + '{"zones":{"myzone":{"subnets":{"' + . $subnet_cidr + . '":{"ips":{"' + . $ipnextfree + . '":{"hostname":"' . $hostname . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"},"' - . $ip2 - . '":{"hostname":"' - . $hostname - . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"}}}}}}}'; + . $mac + . '","vmid":"' + . $vmid . '"},"' + . $ip2 + . '":{"hostname":"' + . $hostname + . '","mac":"' + . $mac + . '","vmid":"' + . $vmid + . '"}}}}}}}'; eval { PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname); }; if ($@) { - fail("$name : $@"); + fail("$name : $@"); } elsif ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); } else { - is(undef, undef, $name); + 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 : $@"); - } + ## 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; - }, + 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 - . '":{"hostname":"' + '{"zones":{"myzone":{"subnets":{"' + . $subnet_cidr + . '":{"ips":{"' + . $ipnextfree + . '":{"hostname":"' + . $hostname + . '","mac":"' + . $mac + . '","vmid":"' + . $vmid . '"},"' + . $ip2 + . '":{"hostname":"' . $hostname . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"},"' - . $ip2 - . '":{"hostname":"' - . $hostname - . '","mac":"' - . $mac - . '","vmid":"' - . $vmid - . '"}}}}}}}'; + . $mac + . '","vmid":"' + . $vmid + . '"}}}}}}}'; eval { - PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid); + PVE::Network::SDN::Subnets::add_ip( + $zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, + ); }; if ($@) { - if ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); - } else { - is(undef, undef, $name); - } + if ($ipam) { + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); + } else { + is(undef, undef, $name); + } } else { - fail("$name : $@"); + fail("$name : $@"); } ## del_empty_subnet @@ -383,12 +387,12 @@ foreach my $path (@plugins) { eval { PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet); }; if ($@) { - fail("$name : $@"); + fail("$name : $@"); } elsif ($ipam) { - $result = $js->encode($plugin->read_db()); - is($result, $expected, $name); + $result = $js->encode($plugin->read_db()); + is($result, $expected, $name); } else { - is(undef, undef, $name); + is(undef, undef, $name); } } diff --git a/src/test/run_test_vnets_blackbox.pl b/src/test/run_test_vnets_blackbox.pl index b79e1dc..2e1c505 100755 --- a/src/test/run_test_vnets_blackbox.pl +++ b/src/test/run_test_vnets_blackbox.pl @@ -30,24 +30,25 @@ use PVE::API2::Network::SDN::Ipams; my $TMP_ETHERS_FILE = "/tmp/ethers"; my $test_state = undef; + sub clear_test_state { $test_state = { - locks => {}, - datacenter_config => {}, - subnets_config => {}, - controller_config => {}, - dns_config => {}, - zones_config => {}, - vnets_config => {}, - macdb => {}, - ipamdb => {}, - ipam_config => { - 'ids' => { - 'pve' => { - 'type' => 'pve' - }, - } - }, + locks => {}, + datacenter_config => {}, + subnets_config => {}, + controller_config => {}, + dns_config => {}, + zones_config => {}, + vnets_config => {}, + macdb => {}, + ipamdb => {}, + ipam_config => { + 'ids' => { + 'pve' => { + 'type' => 'pve', + }, + }, + }, }; PVE::Tools::file_set_contents($TMP_ETHERS_FILE, "\n"); } @@ -73,8 +74,8 @@ sub read_sdn_config { open my $in, '<', $file or die $!; my $sdn_config; { - local $/; # slurp mode - $sdn_config = eval <$in>; + local $/; # slurp mode + $sdn_config = eval <$in>; } close $in; return $sdn_config; @@ -95,11 +96,11 @@ my $mocked_sdn_zones; $mocked_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones'); $mocked_sdn_zones->mock( config => sub { - return $test_state->{zones_config}; + return $test_state->{zones_config}; }, write_config => sub { - my ($cfg) = @_; - $test_state->{zones_config} = $cfg; + my ($cfg) = @_; + $test_state->{zones_config} = $cfg; }, ); @@ -107,7 +108,7 @@ my $mocked_sdn_zones_super_plugin; $mocked_sdn_zones_super_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin'); $mocked_sdn_zones_super_plugin->mock( datacenter_config => sub { - return $test_state->{datacenter_config}; + return $test_state->{datacenter_config}; }, ); @@ -115,11 +116,11 @@ my $mocked_sdn_vnets; $mocked_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets'); $mocked_sdn_vnets->mock( config => sub { - return $test_state->{vnets_config}; + return $test_state->{vnets_config}; }, write_config => sub { - my ($cfg) = @_; - $test_state->{vnets_config} = $cfg; + my ($cfg) = @_; + $test_state->{vnets_config} = $cfg; }, cfs_lock_file => $mocked_cfs_lock_file, ); @@ -128,11 +129,11 @@ my $mocked_sdn_subnets; $mocked_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets'); $mocked_sdn_subnets->mock( config => sub { - return $test_state->{subnets_config}; + return $test_state->{subnets_config}; }, write_config => sub { - my ($cfg) = @_; - $test_state->{subnets_config} = $cfg; + my ($cfg) = @_; + $test_state->{subnets_config} = $cfg; }, cfs_lock_file => $mocked_cfs_lock_file, ); @@ -141,46 +142,44 @@ my $mocked_sdn_controller; $mocked_sdn_controller = Test::MockModule->new('PVE::Network::SDN::Controllers'); $mocked_sdn_controller->mock( config => sub { - return $test_state->{controller_config}; + return $test_state->{controller_config}; }, write_config => sub { - my ($cfg) = @_; - $test_state->{controller_config} = $cfg; + my ($cfg) = @_; + $test_state->{controller_config} = $cfg; }, cfs_lock_file => $mocked_cfs_lock_file, ); - my $mocked_sdn_dns; $mocked_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns'); $mocked_sdn_dns->mock( config => sub { - return $test_state->{dns_config}; + return $test_state->{dns_config}; }, write_config => sub { - my ($cfg) = @_; - $test_state->{dns_config} = $cfg; + my ($cfg) = @_; + $test_state->{dns_config} = $cfg; }, cfs_lock_file => $mocked_cfs_lock_file, ); - my $mocked_sdn_ipams; $mocked_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams'); $mocked_sdn_ipams->mock( config => sub { - return $test_state->{ipam_config}; + return $test_state->{ipam_config}; }, write_config => sub { - my ($cfg) = @_; - $test_state->{ipam_config} = $cfg; + my ($cfg) = @_; + $test_state->{ipam_config} = $cfg; }, read_macdb => sub { - return $test_state->{macdb}; + return $test_state->{macdb}; }, write_macdb => sub { - my ($cfg) = @_; - $test_state->{macdb} = $cfg; + my ($cfg) = @_; + $test_state->{macdb} = $cfg; }, cfs_lock_file => $mocked_cfs_lock_file, ); @@ -189,11 +188,11 @@ my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup("pve"); # NOTE this i my $mocked_ipam_plugin = Test::MockModule->new($ipam_plugin); $mocked_ipam_plugin->mock( read_db => sub { - return $test_state->{ipamdb}; + return $test_state->{ipamdb}; }, write_db => sub { - my ($cfg) = @_; - $test_state->{ipamdb} = $cfg; + my ($cfg) = @_; + $test_state->{ipamdb} = $cfg; }, cfs_lock_file => $mocked_cfs_lock_file, ); @@ -201,15 +200,15 @@ $mocked_ipam_plugin->mock( my $mocked_sdn_dhcp_dnsmasq = Test::MockModule->new('PVE::Network::SDN::Dhcp::Dnsmasq'); $mocked_sdn_dhcp_dnsmasq->mock( assert_dnsmasq_installed => sub { return 1; }, - before_configure => sub {}, + before_configure => sub { }, ethers_file => sub { return "/tmp/ethers"; }, - systemctl_service => sub {}, - update_lease => sub {}, + systemctl_service => sub { }, + update_lease => sub { }, ); my $mocked_api_zones = Test::MockModule->new('PVE::API2::Network::SDN::Zones'); $mocked_api_zones->mock( - create_etc_interfaces_sdn_dir => sub {}, + create_etc_interfaces_sdn_dir => sub { }, ); my $rpcenv = PVE::RESTEnvironment->init('priv'); @@ -231,7 +230,8 @@ $mocked_pve_cluster_obj->mock( sub nic_join { my ($vnetid, $mac, $hostname, $vmid) = @_; - return PVE::Network::SDN::Vnets::add_next_free_cidr($vnetid, $hostname, $mac, "$vmid", undef, 1); + return PVE::Network::SDN::Vnets::add_next_free_cidr($vnetid, $hostname, $mac, "$vmid", undef, + 1); } sub nic_leave { @@ -244,13 +244,13 @@ sub nic_start { return PVE::Network::SDN::Vnets::add_dhcp_mapping($vnetid, $mac, $vmid, $hostname); } - # ---- API HELPER FUNCTIONS FOR THE TESTS ----- my $t_invalid; + sub get_zone { my ($id) = @_; - return eval { PVE::API2::Network::SDN::Zones->read({zone => $id}); }; + return eval { PVE::API2::Network::SDN::Zones->read({ zone => $id }); }; } # verify get_zone actually fails if invalid $t_invalid = get_zone("invalid"); @@ -265,13 +265,13 @@ sub create_zone { die("creating zone failed: $@") if ($@); my $zone = get_zone($zoneid); - die ("test setup: zone ($zoneid) not defined") if (!defined $zone); + die("test setup: zone ($zoneid) not defined") if (!defined $zone); return $zone; } sub get_vnet { my ($id) = @_; - return eval { PVE::API2::Network::SDN::Vnets->read({vnet => $id}); }; + return eval { PVE::API2::Network::SDN::Vnets->read({ vnet => $id }); }; } # verify get_vnet $t_invalid = get_vnet("invalid"); @@ -284,13 +284,13 @@ sub create_vnet { PVE::API2::Network::SDN::Vnets->create($params); my $vnet = get_vnet($vnetid); - die ("test setup: vnet ($vnetid) not defined") if (!defined $vnet); + die("test setup: vnet ($vnetid) not defined") if (!defined $vnet); return $vnet; } sub get_subnet { my ($id) = @_; - return eval { PVE::API2::Network::SDN::Subnets->read({subnet => $id}); }; + return eval { PVE::API2::Network::SDN::Subnets->read({ subnet => $id }); }; } # verify get_subnet $t_invalid = get_subnet("invalid"); @@ -303,7 +303,7 @@ sub create_subnet { } sub get_ipam_entries { - return PVE::API2::Network::SDN::Ipams->ipamindex({ipam => "pve"}); + return PVE::API2::Network::SDN::Ipams->ipamindex({ ipam => "pve" }); } sub create_ip { @@ -333,560 +333,609 @@ sub get_ip6 { return $ip6->{ip} if defined $ip6; } - # -------------- ACTUAL TESTS ----------------------- sub test_create_vnet_with_gateway { - my $test_name = (split(/::/,(caller(0))[3]))[-1]; + my $test_name = (split(/::/, (caller(0))[3]))[-1]; my $zoneid = "TESTZONE"; my $vnetid = "testvnet"; my $zone = create_zone({ - type => "simple", - dhcp => "dnsmasq", - ipam => "pve", - zone => $zoneid, + type => "simple", + dhcp => "dnsmasq", + ipam => "pve", + zone => $zoneid, }); my $vnet = create_vnet({ - type => "vnet", - zone => $zoneid, - vnet => $vnetid, + type => "vnet", + zone => $zoneid, + vnet => $vnetid, }); create_subnet({ - type => "subnet", - vnet => $vnetid, - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + type => "subnet", + vnet => $vnetid, + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], }); my ($p) = first { $_->{gateway} == 1 } get_ipam_entries()->@*; - ok ($p, "$test_name: Gateway IP was created in IPAM"); + ok($p, "$test_name: Gateway IP was created in IPAM"); } run_test(\&test_create_vnet_with_gateway); - sub test_without_subnet { - my $test_name = (split(/::/,(caller(0))[3]))[-1]; + my $test_name = (split(/::/, (caller(0))[3]))[-1]; my $zoneid = "TESTZONE"; my $vnetid = "testvnet"; my $zone = create_zone({ - type => "simple", - dhcp => "dnsmasq", - ipam => "pve", - zone => $zoneid, + type => "simple", + dhcp => "dnsmasq", + ipam => "pve", + zone => $zoneid, }); my $vnet = create_vnet({ - type => "vnet", - zone => $zoneid, - vnet => $vnetid, + type => "vnet", + zone => $zoneid, + vnet => $vnetid, }); my $hostname = "testhostname"; my $mac = "da:65:8f:18:9b:6f"; my $vmid = "999"; - eval { - nic_join($vnetid, $mac, $hostname, $vmid); - }; + eval { nic_join($vnetid, $mac, $hostname, $vmid); }; if ($@) { - fail("$test_name: $@"); - return; + fail("$test_name: $@"); + return; } my @ips = get_ips_from_mac($mac); my $num_ips = scalar @ips; - is ($num_ips, 0, "$test_name: No IP allocated in IPAM"); + is($num_ips, 0, "$test_name: No IP allocated in IPAM"); } run_test(\&test_without_subnet); - sub test_nic_join { my ($test_name, $subnets) = @_; die "$test_name: we're expecting an array of subnets" if !$subnets; my $num_subnets = scalar $subnets->@*; - die "$test_name: we're expecting an array of subnets. $num_subnets elements found" if ($num_subnets < 1); + die "$test_name: we're expecting an array of subnets. $num_subnets elements found" + if ($num_subnets < 1); my $zoneid = "TESTZONE"; my $vnetid = "testvnet"; my $zone = create_zone({ - type => "simple", - dhcp => "dnsmasq", - ipam => "pve", - zone => $zoneid, + type => "simple", + dhcp => "dnsmasq", + ipam => "pve", + zone => $zoneid, }); my $vnet = create_vnet({ - type => "vnet", - zone => $zoneid, - vnet => $vnetid, + type => "vnet", + zone => $zoneid, + vnet => $vnetid, }); foreach my $subnet ($subnets->@*) { - $subnet->{type} = "subnet"; - $subnet->{vnet} = $vnetid; - create_subnet($subnet); - }; + $subnet->{type} = "subnet"; + $subnet->{vnet} = $vnetid; + create_subnet($subnet); + } my $hostname = "testhostname"; my $mac = "da:65:8f:18:9b:6f"; my $vmid = "999"; - eval { - nic_join($vnetid, $mac, $hostname, $vmid); - }; + eval { nic_join($vnetid, $mac, $hostname, $vmid); }; if ($@) { - fail("$test_name: $@"); - return; + fail("$test_name: $@"); + return; } my @ips = get_ips_from_mac($mac); my $num_ips = scalar @ips; - is ($num_ips, $num_subnets, "$test_name: Expecting $num_subnets IPs, found $num_ips"); - ok ((all { ($_->{vnet} eq $vnetid && $_->{zone} eq $zoneid) } @ips), - "$test_name: all IPs in correct vnet and zone" + is($num_ips, $num_subnets, "$test_name: Expecting $num_subnets IPs, found $num_ips"); + ok( + (all { ($_->{vnet} eq $vnetid && $_->{zone} eq $zoneid) } @ips), + "$test_name: all IPs in correct vnet and zone", ); } run_test( \&test_nic_join, "nic_join IPv4 no dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + }, + ], +); run_test( \&test_nic_join, "nic_join IPv6 no dhcp", - [{ - subnet => "8888::/64", - gateway => "8888::1", - }, -]); + [ + { + subnet => "8888::/64", + gateway => "8888::1", + }, + ], +); run_test( \&test_nic_join, "nic_join IPv4+6 no dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - }, { - subnet => "8888::/64", - gateway => "8888::1", - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + }, + { + subnet => "8888::/64", + gateway => "8888::1", + }, + ], +); run_test( \&test_nic_join, "nic_join IPv4 with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + ], +); run_test( \&test_nic_join, "nic_join IPv6 with dhcp", - [{ - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }, -]); + [ + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], +); run_test( \&test_nic_join, "nic_join IPv4+6 with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], +); run_test( \&test_nic_join, "nic_join IPv4 no DHCP, IPv6 with DHCP", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], +); run_test( \&test_nic_join, "nic_join IPv4 with DHCP, IPv6 no DHCP", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - }, -]); - + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + }, + ], +); sub test_nic_join_full_dhcp_range { my ($test_name, $subnets, $expected_ip4, $expected_ip6) = @_; die "$test_name: we're expecting an array of subnets" if !$subnets; my $num_subnets = scalar $subnets->@*; - die "$test_name: we're expecting an array of subnets. $num_subnets elements found" if ($num_subnets < 1); + die "$test_name: we're expecting an array of subnets. $num_subnets elements found" + if ($num_subnets < 1); my $zoneid = "TESTZONE"; my $vnetid = "testvnet"; my $zone = create_zone({ - type => "simple", - dhcp => "dnsmasq", - ipam => "pve", - zone => $zoneid, + type => "simple", + dhcp => "dnsmasq", + ipam => "pve", + zone => $zoneid, }); my $vnet = create_vnet({ - type => "vnet", - zone => $zoneid, - vnet => $vnetid, + type => "vnet", + zone => $zoneid, + vnet => $vnetid, }); foreach my $subnet ($subnets->@*) { - $subnet->{type} = "subnet"; - $subnet->{vnet} = $vnetid; - create_subnet($subnet); - }; + $subnet->{type} = "subnet"; + $subnet->{vnet} = $vnetid; + create_subnet($subnet); + } my $hostname = "testhostname"; my $mac = "da:65:8f:18:9b:6f"; my $vmid = "999"; - eval { - nic_join($vnetid, $mac, $hostname, $vmid); - }; + eval { nic_join($vnetid, $mac, $hostname, $vmid); }; - if (! $@) { - fail ("$test_name: nic_join() is expected to fail because we cannot allocate all IPs"); + if (!$@) { + fail("$test_name: nic_join() is expected to fail because we cannot allocate all IPs"); } my @ips = get_ips_from_mac($mac); my $num_ips = scalar @ips; - is ($num_ips, 0, "$test_name: No IP allocated in IPAM"); + is($num_ips, 0, "$test_name: No IP allocated in IPAM"); } run_test( \&test_nic_join_full_dhcp_range, "nic_join IPv4 with DHCP, dhcp-range full", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.100", # the gateway uses the only available IP in the dhcp-range - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], - } -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.100", # the gateway uses the only available IP in the dhcp-range + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], + }, + ], +); run_test( \&test_nic_join_full_dhcp_range, "nic_join IPv6 with DHCP, dhcp-range full", - [{ - subnet => "8888::/64", - gateway => "8888::100", # the gateway uses the only available IP in the dhcp-range - 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], - }, -]); + [ + { + subnet => "8888::/64", + gateway => "8888::100", # the gateway uses the only available IP in the dhcp-range + 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], + }, + ], +); run_test( \&test_nic_join_full_dhcp_range, "nic_join IPv4+6 with DHCP, dhcp-range full for both", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.100", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], - }, { - subnet => "8888::/64", - gateway => "8888::100", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], - } -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.100", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], + }, + { + subnet => "8888::/64", + gateway => "8888::100", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], + }, + ], +); run_test( \&test_nic_join_full_dhcp_range, "nic_join IPv4+6 with DHCP, dhcp-range full for IPv4", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.100", # the gateway uses the only available IP in the dhcp-range - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], - }], + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.100", # the gateway uses the only available IP in the dhcp-range + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], + }, + ], ); run_test( \&test_nic_join_full_dhcp_range, "nic_join IPv4+6 with DHCP, dhcp-range full for IPv6", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], - }, { - subnet => "8888::/64", - gateway => "8888::100", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], - }], + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"], + }, + { + subnet => "8888::/64", + gateway => "8888::100", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], + }, + ], ); run_test( \&test_nic_join_full_dhcp_range, "nic_join IPv4 no DHCP, dhcp-range full for IPv6", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - }, { - subnet => "8888::/64", - gateway => "8888::100", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], - }], + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + }, + { + subnet => "8888::/64", + gateway => "8888::100", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"], + }, + ], ); - # -------------- nic_start sub test_nic_start { my ($test_name, $subnets, $current_ip4, $current_ip6, $num_expected_ips) = @_; die "$test_name: we're expecting an array of subnets" if !$subnets; my $num_subnets = scalar $subnets->@*; - die "$test_name: we're expecting an array of subnets. $num_subnets elements found" if ($num_subnets < 1); + die "$test_name: we're expecting an array of subnets. $num_subnets elements found" + if ($num_subnets < 1); $num_expected_ips = $num_subnets if !defined $num_expected_ips; my $zoneid = "TESTZONE"; my $vnetid = "testvnet"; my $zone = create_zone({ - type => "simple", - dhcp => "dnsmasq", - ipam => "pve", - zone => $zoneid, + type => "simple", + dhcp => "dnsmasq", + ipam => "pve", + zone => $zoneid, }); my $vnet = create_vnet({ - type => "vnet", - zone => $zoneid, - vnet => $vnetid, + type => "vnet", + zone => $zoneid, + vnet => $vnetid, }); foreach my $subnet ($subnets->@*) { - $subnet->{type} = "subnet"; - $subnet->{vnet} = $vnetid; - create_subnet($subnet); - }; + $subnet->{type} = "subnet"; + $subnet->{vnet} = $vnetid; + create_subnet($subnet); + } my $hostname = "testhostname"; my $mac = "da:65:8f:18:9b:6f"; my $vmid = "999"; if ($current_ip4) { - create_ip({ - zone => $zoneid, - vnet => $vnetid, - mac => $mac, - ip => $current_ip4, - }); + create_ip({ + zone => $zoneid, + vnet => $vnetid, + mac => $mac, + ip => $current_ip4, + }); } if ($current_ip6) { - create_ip({ - zone => $zoneid, - vnet => $vnetid, - mac => $mac, - ip => $current_ip6, - }); + create_ip({ + zone => $zoneid, + vnet => $vnetid, + mac => $mac, + ip => $current_ip6, + }); } my @current_ips = get_ips_from_mac($mac); - is ( get_ip4(@current_ips), $current_ip4, "$test_name: setup current IPv4: $current_ip4" ) if defined $current_ip4; - is ( get_ip6(@current_ips), $current_ip6, "$test_name: setup current IPv6: $current_ip6" ) if defined $current_ip6; + is(get_ip4(@current_ips), $current_ip4, "$test_name: setup current IPv4: $current_ip4") + if defined $current_ip4; + is(get_ip6(@current_ips), $current_ip6, "$test_name: setup current IPv6: $current_ip6") + if defined $current_ip6; - eval { - nic_start($vnetid, $mac, $hostname, $vmid); - }; + eval { nic_start($vnetid, $mac, $hostname, $vmid); }; if ($@) { - fail("$test_name: $@"); - return; + fail("$test_name: $@"); + return; } my @ips = get_ips_from_mac($mac); my $num_ips = scalar @ips; - is ($num_ips, $num_expected_ips, "$test_name: Expecting $num_expected_ips IPs, found $num_ips"); - ok ((all { ($_->{vnet} eq $vnetid && $_->{zone} eq $zoneid) } @ips), - "$test_name: all IPs in correct vnet and zone" + is($num_ips, $num_expected_ips, "$test_name: Expecting $num_expected_ips IPs, found $num_ips"); + ok( + (all { ($_->{vnet} eq $vnetid && $_->{zone} eq $zoneid) } @ips), + "$test_name: all IPs in correct vnet and zone", ); - is ( get_ip4(@ips), $current_ip4, "$test_name: still current IPv4: $current_ip4" ) if $current_ip4; - is ( get_ip6(@ips), $current_ip6, "$test_name: still current IPv6: $current_ip6" ) if $current_ip6; + is(get_ip4(@ips), $current_ip4, "$test_name: still current IPv4: $current_ip4") if $current_ip4; + is(get_ip6(@ips), $current_ip6, "$test_name: still current IPv6: $current_ip6") if $current_ip6; } run_test( \&test_nic_start, "nic_start no IP, IPv4 without dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + }, + ], +); run_test( \&test_nic_start, "nic_start already IP, IPv4 without dhcp", [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", }], "10.0.0.99", undef, - 1 + 1, ); run_test( \&test_nic_start, "nic_start already IPv6, IPv6 without dhcp", [{ - subnet => "8888::/64", - gateway => "8888::1", + subnet => "8888::/64", + gateway => "8888::1", }], undef, "8888::99", - 1 + 1, ); run_test( \&test_nic_start, "nic_start no IP, IPv4 subnet with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + ], +); run_test( \&test_nic_start, "nic_start already IP, IPv4 subnet with dhcp", [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], }], - "10.0.0.99" + "10.0.0.99", ); run_test( \&test_nic_start, "nic_start already IP, IPv6 subnet with dhcp", [{ - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], }], undef, - "8888::99" + "8888::99", ); run_test( \&test_nic_start, "nic_start IP, IPv4+6 subnet with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }, -]); + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], +); run_test( \&test_nic_start, "nic_start already IPv4, IPv4+6 subnet with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }], - "10.0.0.99" + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], + "10.0.0.99", ); run_test( \&test_nic_start, "nic_start already IPv6, IPv4+6 subnet with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }], + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], undef, - "8888::99" + "8888::99", ); run_test( \&test_nic_start, "nic_start already IPv4+6, IPv4+6 subnets with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], - }], + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"], + }, + ], "10.0.0.99", - "8888::99" + "8888::99", ); run_test( \&test_nic_start, "nic_start already IPv4+6, only IPv4 subnet with dhcp", - [{ - subnet => "10.0.0.0/24", - gateway => "10.0.0.1", - 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], - }, { - subnet => "8888::/64", - gateway => "8888::1", - }], + [ + { + subnet => "10.0.0.0/24", + gateway => "10.0.0.1", + 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"], + }, + { + subnet => "8888::/64", + gateway => "8888::1", + }, + ], "10.0.0.99", "8888::99", - 2 + 2, ); done_testing(); diff --git a/src/test/run_test_zones.pl b/src/test/run_test_zones.pl index e506bea..86aa0ea 100755 --- a/src/test/run_test_zones.pl +++ b/src/test/run_test_zones.pl @@ -21,8 +21,8 @@ sub read_sdn_config { open my $in, '<', $file or die $!; my $sdn_config; { - local $/; # slurp mode - $sdn_config = eval <$in>; + local $/; # slurp mode + $sdn_config = eval <$in>; } close $in; @@ -42,88 +42,88 @@ foreach my $test (@tests) { my $pve_common_inotify; $pve_common_inotify = Test::MockModule->new('PVE::INotify'); $pve_common_inotify->mock( - nodename => sub { - return 'localhost'; - }, - read_file => sub { - # HACK this assumes we are always calling PVE::INotify::read_file('interfaces'); - return $interfaces_config; - }, - read_etc_network_interfaces => sub { - return $interfaces_config; - }, + nodename => sub { + return 'localhost'; + }, + read_file => sub { + # HACK this assumes we are always calling PVE::INotify::read_file('interfaces'); + return $interfaces_config; + }, + read_etc_network_interfaces => sub { + return $interfaces_config; + }, ); my $mocked_pve_sdn_controllers; $mocked_pve_sdn_controllers = Test::MockModule->new('PVE::Network::SDN::Controllers'); $mocked_pve_sdn_controllers->mock( - read_etc_network_interfaces => sub { - return $interfaces_config; - } + read_etc_network_interfaces => 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}; - }, + 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; - }, + 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; - }, + running_config => sub { + return $sdn_config; + }, ); my $pve_sdn_controllers_plugin; $pve_sdn_controllers_plugin = Test::MockModule->new('PVE::Network::SDN::Controllers::Plugin'); $pve_sdn_controllers_plugin->mock( - read_iface_mac => sub { - return "bc:24:11:1d:69:60"; - }, + read_iface_mac => sub { + return "bc:24:11:1d:69:60"; + }, ); - my ($first_plugin) = %{$sdn_config->{controllers}->{ids}} if defined $sdn_config->{controllers}; + my ($first_plugin) = %{ $sdn_config->{controllers}->{ids} } + if defined $sdn_config->{controllers}; if ($first_plugin) { - my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup( - $sdn_config->{controllers}->{ids}->{$first_plugin}->{type} - ); - my $mocked_controller_plugin = Test::MockModule->new($controller_plugin); - $mocked_controller_plugin->mock( - write_controller_config => sub { - return; - }, - reload_controller => sub { - return; - }, - read_local_frr_config => sub { - return; - }, - ); + my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup( + $sdn_config->{controllers}->{ids}->{$first_plugin}->{type}); + my $mocked_controller_plugin = Test::MockModule->new($controller_plugin); + $mocked_controller_plugin->mock( + write_controller_config => sub { + return; + }, + reload_controller => sub { + return; + }, + read_local_frr_config => sub { + return; + }, + ); } my $name = $test; @@ -132,27 +132,27 @@ foreach my $test (@tests) { my $result = eval { PVE::Network::SDN::Zones::generate_etc_network_config() }; if (my $err = $@) { - diag("got unexpected error - $err"); - fail($name); + diag("got unexpected error - $err"); + fail($name); } else { - is($result, $expected, $name); + 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 = $@) { - diag("got unexpected error - $err"); - fail($name); - } else { - is($controller_rawconfig, $expected, $name); - } + 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 = $@) { + diag("got unexpected error - $err"); + fail($name); + } else { + is($controller_rawconfig, $expected, $name); + } } } |
