diff options
Diffstat (limited to 'src/PVE/API2/Network')
| -rw-r--r-- | src/PVE/API2/Network/Makefile | 9 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN.pm | 144 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Controllers.pm | 290 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Dns.pm | 242 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Ipams.pm | 248 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Makefile | 10 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Subnets.pm | 303 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Vnets.pm | 292 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Zones.pm | 351 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Zones/Content.pm | 85 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Zones/Makefile | 8 | ||||
| -rw-r--r-- | src/PVE/API2/Network/SDN/Zones/Status.pm | 111 |
12 files changed, 2093 insertions, 0 deletions
diff --git a/src/PVE/API2/Network/Makefile b/src/PVE/API2/Network/Makefile new file mode 100644 index 0000000..396f79d --- /dev/null +++ b/src/PVE/API2/Network/Makefile @@ -0,0 +1,9 @@ +SOURCES=SDN.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/$$i; done + make -C SDN install diff --git a/src/PVE/API2/Network/SDN.pm b/src/PVE/API2/Network/SDN.pm new file mode 100644 index 0000000..f129d60 --- /dev/null +++ b/src/PVE/API2/Network/SDN.pm @@ -0,0 +1,144 @@ +package PVE::API2::Network::SDN; + +use strict; +use warnings; + +use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file); +use PVE::Exception qw(raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RESTHandler; +use PVE::RPCEnvironment; +use PVE::SafeSyslog; +use PVE::Tools qw(run_command); +use PVE::Network::SDN; + +use PVE::API2::Network::SDN::Controllers; +use PVE::API2::Network::SDN::Vnets; +use PVE::API2::Network::SDN::Zones; +use PVE::API2::Network::SDN::Ipams; +use PVE::API2::Network::SDN::Dns; + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Vnets", + path => 'vnets', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Zones", + path => 'zones', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Controllers", + path => 'controllers', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Ipams", + path => 'ipams', +}); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Dns", + path => 'dns', +}); + +__PACKAGE__->register_method({ + name => 'index', + path => '', + method => 'GET', + description => "Directory index.", + permissions => { + check => ['perm', '/', [ 'SDN.Audit' ]], + }, + parameters => { + additionalProperties => 0, + properties => {}, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + id => { type => 'string' }, + }, + }, + links => [ { rel => 'child', href => "{id}" } ], + }, + code => sub { + my ($param) = @_; + + my $res = [ + { id => 'vnets' }, + { id => 'zones' }, + { id => 'controllers' }, + { id => 'ipams' }, + { id => 'dns' }, + ]; + + return $res; + }}); + +my $create_reload_network_worker = sub { + my ($nodename) = @_; + + # FIXME: how to proxy to final node ? + my $upid; + run_command(['pvesh', 'set', "/nodes/$nodename/network"], outfunc => sub { + my $line = shift; + if ($line =~ /^["']?(UPID:[^\s"']+)["']?$/) { + $upid = $1; + } + }); + #my $upid = PVE::API2::Network->reload_network_config(node => $nodename}); + my $res = PVE::Tools::upid_decode($upid); + + return $res->{pid}; +}; + +__PACKAGE__->register_method ({ + name => 'reload', + protected => 1, + path => '', + method => 'PUT', + description => "Apply sdn controller changes && reload.", + permissions => { + check => ['perm', '/sdn', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + }, + returns => { + type => 'string', + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + PVE::Network::SDN::commit_config(); + + my $code = sub { + $rpcenv->{type} = 'priv'; # to start tasks in background + PVE::Cluster::check_cfs_quorum(); + my $nodelist = PVE::Cluster::get_nodelist(); + for my $node (@$nodelist) { + my $pid = eval { $create_reload_network_worker->($node) }; + warn $@ if $@; + } + + # FIXME: use libpve-apiclient (like in cluster join) to create + # tasks and moitor the tasks. + + return; + }; + + return $rpcenv->fork_worker('reloadnetworkall', undef, $authuser, $code); + + }}); + + +1; diff --git a/src/PVE/API2/Network/SDN/Controllers.pm b/src/PVE/API2/Network/SDN/Controllers.pm new file mode 100644 index 0000000..d8f18ab --- /dev/null +++ b/src/PVE/API2/Network/SDN/Controllers.pm @@ -0,0 +1,290 @@ +package PVE::API2::Network::SDN::Controllers; + +use strict; +use warnings; + +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Controllers; +use PVE::Network::SDN::Controllers::Plugin; +use PVE::Network::SDN::Controllers::EvpnPlugin; +use PVE::Network::SDN::Controllers::BgpPlugin; +use PVE::Network::SDN::Controllers::FaucetPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $sdn_controllers_type_enum = PVE::Network::SDN::Controllers::Plugin->lookup_types(); + +my $api_sdn_controllers_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id)); + $scfg->{controller} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN controllers index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/controllers/<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.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { controller => { type => 'string' }, + type => { type => 'string' }, + state => { type => 'string', optional => 1 }, + pending => { optional => 1}, + }, + }, + links => [ { rel => 'child', href => "{controller}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Controllers::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{controllers}; + } else { + $cfg = PVE::Network::SDN::Controllers::config(); + } + + my @sids = PVE::Network::SDN::Controllers::sdn_controllers_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/controllers/$id", $privs, 1); + + my $scfg = &$api_sdn_controllers_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{controller}', + method => 'GET', + description => "Read sdn controller configuration.", + permissions => { + check => ['perm', '/sdn/controllers/{controller}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + controller => get_standard_option('pve-sdn-controller-id'), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Controllers::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{controllers}; + } else { + $cfg = PVE::Network::SDN::Controllers::config(); + } + + return &$api_sdn_controllers_config($cfg, $param->{controller}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn controller object.", + permissions => { + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'controller'); + + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id, 1)) { + die "sdn controller object ID '$id' already defined\n"; + } + + $controller_cfg->{ids}->{$id} = $opts; + $plugin->on_update_hook($id, $controller_cfg); + + PVE::Network::SDN::Controllers::write_config($controller_cfg); + + }, "create sdn controller object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{controller}', + method => 'PUT', + description => "Update sdn controller object configuration.", + permissions => { + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'controller'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + + PVE::SectionConfig::assert_if_modified($controller_cfg, $digest); + + my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id); + + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); + + foreach my $k (%$opts) { + $scfg->{$k} = $opts->{$k}; + } + + $plugin->on_update_hook($id, $controller_cfg); + + PVE::Network::SDN::Controllers::write_config($controller_cfg); + + + }, "update sdn controller object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{controller}', + method => 'DELETE', + description => "Delete sdn controller object configuration.", + permissions => { + check => ['perm', '/sdn/controllers', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + controller => get_standard_option('pve-sdn-controller-id', { + completion => \&PVE::Network::SDN::Controllers::complete_sdn_controllers, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'controller'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Controllers::config(); + + my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type}); + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + + $plugin->on_delete_hook($id, $zone_cfg); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Controllers::write_config($cfg); + + }, "delete sdn controller object failed"); + + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Dns.pm b/src/PVE/API2/Network/SDN/Dns.pm new file mode 100644 index 0000000..3d08552 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Dns.pm @@ -0,0 +1,242 @@ +package PVE::API2::Network::SDN::Dns; + +use strict; +use warnings; + +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Network::SDN; +use PVE::Network::SDN::Dns; +use PVE::Network::SDN::Dns::Plugin; +use PVE::Network::SDN::Dns::PowerdnsPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $sdn_dns_type_enum = PVE::Network::SDN::Dns::Plugin->lookup_types(); + +my $api_sdn_dns_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id)); + $scfg->{dns} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN dns index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/dns/<dns>'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + type => { + description => "Only list sdn dns of specific type", + type => 'string', + enum => $sdn_dns_type_enum, + optional => 1, + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { dns => { type => 'string'}, + type => { type => 'string'}, + }, + }, + links => [ { rel => 'child', href => "{dns}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + + my $cfg = PVE::Network::SDN::Dns::config(); + + my @sids = PVE::Network::SDN::Dns::sdn_dns_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/dns/$id", $privs, 1); + + my $scfg = &$api_sdn_dns_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{dns}', + method => 'GET', + description => "Read sdn dns configuration.", + permissions => { + check => ['perm', '/sdn/dns/{dns}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + dns => get_standard_option('pve-sdn-dns-id'), + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = PVE::Network::SDN::Dns::config(); + + return &$api_sdn_dns_config($cfg, $param->{dns}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn dns object.", + permissions => { + check => ['perm', '/sdn/dns', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Dns::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'dns'); + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id, 1)) { + die "sdn dns object ID '$id' already defined\n"; + } + + $dns_cfg->{ids}->{$id} = $opts; + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($opts->{type}); + $plugin->on_update_hook($opts); + + PVE::Network::SDN::Dns::write_config($dns_cfg); + + }, "create sdn dns object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{dns}', + method => 'PUT', + description => "Update sdn dns object configuration.", + permissions => { + check => ['perm', '/sdn/dns', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'dns'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + PVE::SectionConfig::assert_if_modified($dns_cfg, $digest); + + my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id); + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); + + foreach my $k (%$opts) { + $scfg->{$k} = $opts->{$k}; + } + + $plugin->on_update_hook($scfg); + + PVE::Network::SDN::Dns::write_config($dns_cfg); + + }, "update sdn dns object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{dns}', + method => 'DELETE', + description => "Delete sdn dns object configuration.", + permissions => { + check => ['perm', '/sdn/dns', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + dns => get_standard_option('pve-sdn-dns-id', { + completion => \&PVE::Network::SDN::Dns::complete_sdn_dns, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'dns'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Dns::config(); + + my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type}); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Dns::write_config($cfg); + + }, "delete sdn dns object failed"); + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Ipams.pm b/src/PVE/API2/Network/SDN/Ipams.pm new file mode 100644 index 0000000..6410e8e --- /dev/null +++ b/src/PVE/API2/Network/SDN/Ipams.pm @@ -0,0 +1,248 @@ +package PVE::API2::Network::SDN::Ipams; + +use strict; +use warnings; + +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Network::SDN; +use PVE::Network::SDN::Ipams; +use PVE::Network::SDN::Ipams::Plugin; +use PVE::Network::SDN::Ipams::PVEPlugin; +use PVE::Network::SDN::Ipams::PhpIpamPlugin; +use PVE::Network::SDN::Ipams::NetboxPlugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $sdn_ipams_type_enum = PVE::Network::SDN::Ipams::Plugin->lookup_types(); + +my $api_sdn_ipams_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id)); + $scfg->{ipam} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN ipams index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/ipams/<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, + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { ipam => { type => 'string'}, + type => { type => 'string'}, + }, + }, + links => [ { rel => 'child', href => "{ipam}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + + my $cfg = PVE::Network::SDN::Ipams::config(); + + my @sids = PVE::Network::SDN::Ipams::sdn_ipams_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/ipams/$id", $privs, 1); + + my $scfg = &$api_sdn_ipams_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{ipam}', + method => 'GET', + description => "Read sdn ipam configuration.", + permissions => { + check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + ipam => get_standard_option('pve-sdn-ipam-id'), + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = PVE::Network::SDN::Ipams::config(); + + return &$api_sdn_ipams_config($cfg, $param->{ipam}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn ipam object.", + permissions => { + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'ipam'); + + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id, 1)) { + die "sdn ipam object ID '$id' already defined\n"; + } + + $ipam_cfg->{ids}->{$id} = $opts; + + my $plugin_config = $opts; + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type}); + $plugin->on_update_hook($plugin_config); + + PVE::Network::SDN::Ipams::write_config($ipam_cfg); + + }, "create sdn ipam object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{ipam}', + method => 'PUT', + description => "Update sdn ipam object configuration.", + permissions => { + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'ipam'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + + PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest); + + my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id); + + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); + + foreach my $k (%$opts) { + $scfg->{$k} = $opts->{$k}; + } + + $plugin->on_update_hook($scfg); + + PVE::Network::SDN::Ipams::write_config($ipam_cfg); + + }, "update sdn ipam object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{ipam}', + method => 'DELETE', + description => "Delete sdn ipam object configuration.", + permissions => { + check => ['perm', '/sdn/ipams', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + ipam => get_standard_option('pve-sdn-ipam-id', { + completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'ipam'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Ipams::config(); + + my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type}); + + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Ipams::write_config($cfg); + + }, "delete sdn zone object failed"); + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Makefile b/src/PVE/API2/Network/SDN/Makefile new file mode 100644 index 0000000..3683fa4 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Makefile @@ -0,0 +1,10 @@ +SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/$$i; done + make -C Zones install + diff --git a/src/PVE/API2/Network/SDN/Subnets.pm b/src/PVE/API2/Network/SDN/Subnets.pm new file mode 100644 index 0000000..377a568 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Subnets.pm @@ -0,0 +1,303 @@ +package PVE::API2::Network::SDN::Subnets; + +use strict; +use warnings; + +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Exception qw(raise raise_param_exc); +use PVE::Network::SDN; +use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::SubnetPlugin; +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Ipams; +use PVE::Network::SDN::Ipams::Plugin; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $api_sdn_subnets_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id)); + $scfg->{subnet} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN subnets index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/subnets/<subnet>'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [ { rel => 'child', href => "{subnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $vnetid = $param->{vnet}; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Subnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{subnets}; + } else { + $cfg = PVE::Network::SDN::Subnets::config(); + } + + my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid/subnets/$id", $privs, 1); + + my $scfg = &$api_sdn_subnets_config($cfg, $id); + next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid; + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{subnet}', + method => 'GET', + description => "Read sdn subnet configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets/{subnet}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + subnet => get_standard_option('pve-sdn-subnet-id', { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Subnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{subnets}; + } else { + $cfg = PVE::Network::SDN::Subnets::config(); + } + + my $scfg = &$api_sdn_subnets_config($cfg, $param->{subnet}); + + raise_param_exc({ vnet => "wrong vnet"}) if $param->{vnet} ne $scfg->{vnet}; + + return $scfg; + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn subnet object.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::SubnetPlugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $cidr = extract_param($param, 'subnet'); + + # create /etc/pve/sdn directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn") if ! -d '/etc/pve/sdn'; + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + my $vnet = $param->{vnet}; + my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; + my $id = $cidr =~ s/\//-/r; + $id = "$zoneid-$id"; + + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) { + die "sdn subnet object ID '$id' already defined\n"; + } + + $cfg->{ids}->{$id} = $opts; + + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); + PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet); + + PVE::Network::SDN::Subnets::write_config($cfg); + + }, "create sdn subnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{subnet}', + method => 'PUT', + description => "Update sdn subnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'subnet'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config( + sub { + + my $cfg = PVE::Network::SDN::Subnets::config(); + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + my $vnet = $param->{vnet}; + my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; + + my $scfg = &$api_sdn_subnets_config($cfg, $id); + + PVE::SectionConfig::assert_if_modified($cfg, $digest); + + my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1); + $cfg->{ids}->{$id} = $opts; + + raise_param_exc({ ipam => "you can't change ipam"}) if $opts->{ipam} && $scfg->{ipam} && $opts->{ipam} ne $scfg->{ipam}; + + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id); + PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg); + + PVE::Network::SDN::Subnets::write_config($cfg); + + }, "update sdn subnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{subnet}', + method => 'DELETE', + description => "Delete sdn subnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id'), + subnet => get_standard_option('pve-sdn-subnet-id', { + completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'subnet'); + + PVE::Network::SDN::lock_sdn_config( + sub { + my $cfg = PVE::Network::SDN::Subnets::config(); + + my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1); + + my $vnets_cfg = PVE::Network::SDN::Vnets::config(); + + PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg); + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $vnet = $param->{vnet}; + my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone}; + my $zone = $zone_cfg->{ids}->{$zoneid}; + + PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg); + + delete $cfg->{ids}->{$id}; + + PVE::Network::SDN::Subnets::write_config($cfg); + + }, "delete sdn subnet object failed"); + + + return undef; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Vnets.pm b/src/PVE/API2/Network/SDN/Vnets.pm new file mode 100644 index 0000000..811a2e8 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Vnets.pm @@ -0,0 +1,292 @@ +package PVE::API2::Network::SDN::Vnets; + +use strict; +use warnings; + +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Network::SDN; +use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN::VnetPlugin; +use PVE::Network::SDN::Subnets; +use PVE::API2::Network::SDN::Subnets; + +use Storable qw(dclone); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; +use PVE::Exception qw(raise raise_param_exc); + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Subnets", + path => '{vnet}/subnets', +}); + +my $api_sdn_vnets_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id)); + $scfg->{vnet} = $id; + $scfg->{digest} = $cfg->{digest}; + + return $scfg; +}; + +my $api_sdn_vnets_deleted_config = sub { + my ($cfg, $running_cfg, $id) = @_; + + if (!$cfg->{ids}->{$id}) { + + my $vnet_cfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($running_cfg->{vnets}, $id)); + $vnet_cfg->{state} = "deleted"; + $vnet_cfg->{vnet} = $id; + return $vnet_cfg; + } +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN vnets index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'" + ." permissions on '/sdn/vnets/<vnet>'", + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => { + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [ { rel => 'child', href => "{vnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Vnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{vnets}; + } else { + $cfg = PVE::Network::SDN::Vnets::config(); + } + + my @sids = PVE::Network::SDN::Vnets::sdn_vnets_ids($cfg); + my $res = []; + foreach my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/vnets/$id", $privs, 1); + + my $scfg = &$api_sdn_vnets_config($cfg, $id); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{vnet}', + method => 'GET', + description => "Read sdn vnet configuration.", + permissions => { + check => ['perm', '/sdn/vnets/{vnet}', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id', { + completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, + }), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + }, + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Vnets::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{vnets}; + } else { + $cfg = PVE::Network::SDN::Vnets::config(); + } + + return $api_sdn_vnets_config->($cfg, $param->{vnet}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn vnet object.", + permissions => { + check => ['perm', '/sdn/vnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::VnetPlugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'vnet'); + + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Vnets::config(); + my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 1, 1); + + if (PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id, 1)) { + die "sdn vnet object ID '$id' already defined\n"; + } + $cfg->{ids}->{$id} = $opts; + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $zoneid = $cfg->{ids}->{$id}->{zone}; + my $plugin_config = $zone_cfg->{ids}->{$zoneid}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->vnet_update_hook($cfg, $id, $zone_cfg); + + PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); + + PVE::Network::SDN::Vnets::write_config($cfg); + + }, "create sdn vnet object failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{vnet}', + method => 'PUT', + description => "Update sdn vnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::VnetPlugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'vnet'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Vnets::config(); + + PVE::SectionConfig::assert_if_modified($cfg, $digest); + + + my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1); + raise_param_exc({ zone => "missing zone"}) if !$opts->{zone}; + my $subnets = PVE::Network::SDN::Vnets::get_subnets($id); + raise_param_exc({ zone => "can't change zone if subnets exists"}) if($subnets && $opts->{zone} ne $cfg->{ids}->{$id}->{zone}); + + $cfg->{ids}->{$id} = $opts; + + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $zoneid = $cfg->{ids}->{$id}->{zone}; + my $plugin_config = $zone_cfg->{ids}->{$zoneid}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + $plugin->vnet_update_hook($cfg, $id, $zone_cfg); + + PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); + + PVE::Network::SDN::Vnets::write_config($cfg); + + }, "update sdn vnet object failed"); + + return undef; + } +}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{vnet}', + method => 'DELETE', + description => "Delete sdn vnet object configuration.", + permissions => { + check => ['perm', '/sdn/vnets', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + vnet => get_standard_option('pve-sdn-vnet-id', { + completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'vnet'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Vnets::config(); + my $scfg = PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id); # check if exists + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + + PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg); + + delete $cfg->{ids}->{$id}; + PVE::Network::SDN::Vnets::write_config($cfg); + + }, "delete sdn vnet object failed"); + + + return undef; + } +}); + +1; diff --git a/src/PVE/API2/Network/SDN/Zones.pm b/src/PVE/API2/Network/SDN/Zones.pm new file mode 100644 index 0000000..6e53240 --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones.pm @@ -0,0 +1,351 @@ +package PVE::API2::Network::SDN::Zones; + +use strict; +use warnings; + +use Storable qw(dclone); + +use PVE::Cluster qw(cfs_read_file cfs_write_file); +use PVE::Exception qw(raise raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); +use PVE::RPCEnvironment; +use PVE::SafeSyslog; +use PVE::Tools qw(extract_param); + +use PVE::Network::SDN::Dns; +use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::Vnets; +use PVE::Network::SDN; + +use PVE::Network::SDN::Zones::EvpnPlugin; +use PVE::Network::SDN::Zones::FaucetPlugin; +use PVE::Network::SDN::Zones::Plugin; +use PVE::Network::SDN::Zones::QinQPlugin; +use PVE::Network::SDN::Zones::SimplePlugin; +use PVE::Network::SDN::Zones::VlanPlugin; +use PVE::Network::SDN::Zones::VxlanPlugin; +use PVE::Network::SDN::Zones; + +use PVE::RESTHandler; +use base qw(PVE::RESTHandler); + +my $sdn_zones_type_enum = PVE::Network::SDN::Zones::Plugin->lookup_types(); + +my $api_sdn_zones_config = sub { + my ($cfg, $id) = @_; + + my $scfg = dclone(PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id)); + $scfg->{zone} = $id; + $scfg->{digest} = $cfg->{digest}; + + if ($scfg->{nodes}) { + $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $scfg->{nodes}); + } + + if ($scfg->{exitnodes}) { + $scfg->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $scfg->{exitnodes}); + } + + my $pending = $scfg->{pending}; + if ($pending->{nodes}) { + $pending->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $pending->{nodes}); + } + + if ($pending->{exitnodes}) { + $pending->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes}); + } + + return $scfg; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "SDN zones index.", + permissions => { + description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<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.", + }, + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { zone => { type => 'string'}, + type => { type => 'string'}, + mtu => { type => 'integer', optional => 1 }, + dns => { type => 'string', optional => 1}, + reversedns => { type => 'string', optional => 1}, + dnszone => { type => 'string', optional => 1}, + ipam => { type => 'string', optional => 1}, + pending => { optional => 1}, + state => { type => 'string', optional => 1}, + nodes => { type => 'string', optional => 1}, + }, + }, + links => [ { rel => 'child', href => "{zone}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $cfg = {}; + if ($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Zones::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{zones}; + } else { + $cfg = PVE::Network::SDN::Zones::config(); + } + + my @sids = PVE::Network::SDN::Zones::sdn_zones_ids($cfg); + my $res = []; + for my $id (@sids) { + my $privs = [ 'SDN.Audit', 'SDN.Allocate' ]; + next if !$rpcenv->check_any($authuser, "/sdn/zones/$id", $privs, 1); + + my $scfg = &$api_sdn_zones_config($cfg, $id); + next if $param->{type} && $param->{type} ne $scfg->{type}; + + my $plugin_config = $cfg->{ids}->{$id}; + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type}); + push @$res, $scfg; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'read', + path => '{zone}', + method => 'GET', + description => "Read sdn zone configuration.", + permissions => { + check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']], + }, + + parameters => { + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + running => { + type => 'boolean', + optional => 1, + description => "Display running config.", + }, + pending => { + type => 'boolean', + optional => 1, + description => "Display pending config.", + } + }, + }, + returns => { type => 'object' }, + code => sub { + my ($param) = @_; + + my $cfg = {}; + if ($param->{pending}) { + my $running_cfg = PVE::Network::SDN::running_config(); + my $config = PVE::Network::SDN::Zones::config(); + $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones'); + } elsif ($param->{running}) { + my $running_cfg = PVE::Network::SDN::running_config(); + $cfg = $running_cfg->{zones}; + } else { + $cfg = PVE::Network::SDN::Zones::config(); + } + + return &$api_sdn_zones_config($cfg, $param->{zone}); + }}); + +__PACKAGE__->register_method ({ + name => 'create', + protected => 1, + path => '', + method => 'POST', + description => "Create a new sdn zone object.", + permissions => { + check => ['perm', '/sdn/zones', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Zones::Plugin->createSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'zone'); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); + + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/sdn"); + + PVE::Network::SDN::lock_sdn_config(sub { + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + my $scfg = undef; + if ($scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id, 1)) { + die "sdn zone object ID '$id' already defined\n"; + } + + my $dnsserver = $opts->{dns}; + raise_param_exc({ dns => "$dnsserver don't exist"}) + if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; + + my $reversednsserver = $opts->{reversedns}; + raise_param_exc({ reversedns => "$reversednsserver don't exist"}) + if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver}; + + my $dnszone = $opts->{dnszone}; + raise_param_exc({ dnszone => "missing dns server"}) + if $dnszone && !$dnsserver; + + my $ipam = $opts->{ipam}; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam}; + + $zone_cfg->{ids}->{$id} = $opts; + $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); + + PVE::Network::SDN::Zones::write_config($zone_cfg); + + }, "create sdn zone object failed"); + + return; + }}); + +__PACKAGE__->register_method ({ + name => 'update', + protected => 1, + path => '{zone}', + method => 'PUT', + description => "Update sdn zone object configuration.", + permissions => { + check => ['perm', '/sdn/zones', ['SDN.Allocate']], + }, + parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(), + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'zone'); + my $digest = extract_param($param, 'digest'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $zone_cfg = PVE::Network::SDN::Zones::config(); + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + my $dns_cfg = PVE::Network::SDN::Dns::config(); + + PVE::SectionConfig::assert_if_modified($zone_cfg, $digest); + + my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); + + if ($opts->{ipam} && !$scfg->{ipam} || $opts->{ipam} ne $scfg->{ipam}) { + + # don't allow ipam change if subnet are defined for now, need to implement resync ipam content + my $subnets_cfg = PVE::Network::SDN::Subnets::config(); + for my $subnetid (sort keys %{$subnets_cfg->{ids}}) { + my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); + raise_param_exc({ ipam => "can't change ipam if a subnet is already defined in this zone"}) + if $subnet->{zone} eq $id; + } + } + + $zone_cfg->{ids}->{$id} = $opts; + + my $dnsserver = $opts->{dns}; + raise_param_exc({ dns => "$dnsserver don't exist"}) if $dnsserver && !$dns_cfg->{ids}->{$dnsserver}; + + my $reversednsserver = $opts->{reversedns}; + raise_param_exc({ reversedns => "$reversednsserver don't exist"}) if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver}; + + my $dnszone = $opts->{dnszone}; + raise_param_exc({ dnszone => "missing dns server"}) if $dnszone && !$dnsserver; + + my $ipam = $opts->{ipam}; + my $ipam_cfg = PVE::Network::SDN::Ipams::config(); + raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam}; + + $plugin->on_update_hook($id, $zone_cfg, $controller_cfg); + + PVE::Network::SDN::Zones::write_config($zone_cfg); + + }, "update sdn zone object failed"); + + return; + }}); + +__PACKAGE__->register_method ({ + name => 'delete', + protected => 1, + path => '{zone}', + method => 'DELETE', + description => "Delete sdn zone object configuration.", + permissions => { + check => ['perm', '/sdn/zones', ['SDN.Allocate']], + }, + parameters => { + additionalProperties => 0, + properties => { + zone => get_standard_option('pve-sdn-zone-id', { + completion => \&PVE::Network::SDN::Zones::complete_sdn_zones, + }), + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $id = extract_param($param, 'zone'); + + PVE::Network::SDN::lock_sdn_config(sub { + my $cfg = PVE::Network::SDN::Zones::config(); + my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id); + + my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type}); + my $vnet_cfg = PVE::Network::SDN::Vnets::config(); + + $plugin->on_delete_hook($id, $vnet_cfg); + + delete $cfg->{ids}->{$id}; + + PVE::Network::SDN::Zones::write_config($cfg); + }, "delete sdn zone object failed"); + + return; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Zones/Content.pm b/src/PVE/API2/Network/SDN/Zones/Content.pm new file mode 100644 index 0000000..66f49df --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones/Content.pm @@ -0,0 +1,85 @@ +package PVE::API2::Network::SDN::Zones::Content; + +use strict; +use warnings; +use Data::Dumper; + +use PVE::SafeSyslog; +use PVE::Cluster; +use PVE::INotify; +use PVE::Exception qw(raise_param_exc); +use PVE::RPCEnvironment; +use PVE::RESTHandler; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Network::SDN; + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "List zone content.", + permissions => { + check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], + }, + protected => 1, + proxyto => 'node', + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + zone => get_standard_option('pve-sdn-zone-id', { + completion => \&PVE::Network::SDN::Zones::complete_sdn_zone, + }), + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + vnet => { + description => "Vnet identifier.", + type => 'string', + }, + status => { + description => "Status.", + type => 'string', + optional => 1, + }, + statusmsg => { + description => "Status details", + type => 'string', + optional => 1, + }, + }, + }, + links => [ { rel => 'child', href => "{vnet}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + + my $authuser = $rpcenv->get_user(); + + my $zoneid = $param->{zone}; + + my $res = []; + + my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); + + foreach my $id (keys %{$vnet_status}) { + if ($vnet_status->{$id}->{zone} eq $zoneid) { + my $item->{vnet} = $id; + $item->{status} = $vnet_status->{$id}->{'status'}; + $item->{statusmsg} = $vnet_status->{$id}->{'statusmsg'}; + push @$res,$item; + } + } + + return $res; + }}); + +1; diff --git a/src/PVE/API2/Network/SDN/Zones/Makefile b/src/PVE/API2/Network/SDN/Zones/Makefile new file mode 100644 index 0000000..9b0a42b --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones/Makefile @@ -0,0 +1,8 @@ +SOURCES=Status.pm Content.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/Zones/$$i; done diff --git a/src/PVE/API2/Network/SDN/Zones/Status.pm b/src/PVE/API2/Network/SDN/Zones/Status.pm new file mode 100644 index 0000000..17de68f --- /dev/null +++ b/src/PVE/API2/Network/SDN/Zones/Status.pm @@ -0,0 +1,111 @@ +package PVE::API2::Network::SDN::Zones::Status; + +use strict; +use warnings; + +use File::Path; +use File::Basename; +use PVE::Tools; +use PVE::INotify; +use PVE::Cluster; +use PVE::API2::Network::SDN::Zones::Content; +use PVE::RESTHandler; +use PVE::RPCEnvironment; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Exception qw(raise_param_exc); + +use base qw(PVE::RESTHandler); + +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Network::SDN::Zones::Content", + path => '{zone}/content', +}); + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "Get status for all zones.", + permissions => { + description => "Only list entries where you have 'SDN.Audit'", + user => 'all', + }, + protected => 1, + proxyto => 'node', + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node') + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + zone => get_standard_option('pve-sdn-zone-id'), + status => { + description => "Status of zone", + type => 'string', + enum => ['available', 'pending', 'error'], + }, + }, + }, + links => [ { rel => 'child', href => "{zone}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $localnode = PVE::INotify::nodename(); + + my $res = []; + + my ($zone_status, $vnet_status) = PVE::Network::SDN::status(); + + foreach my $id (sort keys %{$zone_status}) { + my $item->{zone} = $id; + $item->{status} = $zone_status->{$id}->{'status'}; + push @$res, $item; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'diridx', + path => '{zone}', + method => 'GET', + description => "", + permissions => { + check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + zone => get_standard_option('pve-sdn-zone-id'), + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + subdir => { type => 'string' }, + }, + }, + links => [ { rel => 'child', href => "{subdir}" } ], + }, + code => sub { + my ($param) = @_; + my $res = [ + { subdir => 'content' }, + ]; + + return $res; + }}); + +1; |
