From db77631c7976c949fbef5c006f80480d9d7b72cd Mon Sep 17 00:00:00 2001 From: Kirill Solomko Date: Thu, 10 Sep 2026 17:58:12 +0200 Subject: [PATCH] MT#65239 enhance /api/platforminfo with meta release info * /api/platforminfo now returns a new 'release_info' section that contains info about: - latest NGCP release - latest NGCP LTS release - info about the current release such as expiration dates, lts, supported flags. - 'this' subsection contains current release version and also a platform type * caching support, the info is retrieved and parsed only once after the ngcp-panel service restart and then once every hour and only if the content is changed. * NGCP::Panel add Catalyst::Cache and Plugin::Cache support accessible via $c->cache Change-Id: I50c58e2563db5962f498d3523db95b4f6372596c --- lib/NGCP/Panel.pm | 8 ++ lib/NGCP/Panel/Controller/API/Root.pm | 2 + lib/NGCP/Panel/Utils/MetaRelease.pm | 196 ++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 lib/NGCP/Panel/Utils/MetaRelease.pm diff --git a/lib/NGCP/Panel.pm b/lib/NGCP/Panel.pm index 7293ecfba4..b889d14360 100644 --- a/lib/NGCP/Panel.pm +++ b/lib/NGCP/Panel.pm @@ -19,6 +19,7 @@ use IO::Socket::UNIX qw(SOCK_DGRAM); # directory use Catalyst qw/ + Cache ConfigLoader Static::Simple Authentication @@ -113,6 +114,13 @@ __PACKAGE__->config( EVAL_PERL => 1, }, + 'Plugin::Cache' => { + backend => { + class => "Cache::MemoryCache", + namespace => __PACKAGE__, + }, + }, + 'Plugin::Static::Simple' => { include_path => [ '/usr/share/ngcp-panel/static', diff --git a/lib/NGCP/Panel/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm index 01434be480..9a22b97f51 100644 --- a/lib/NGCP/Panel/Controller/API/Root.pm +++ b/lib/NGCP/Panel/Controller/API/Root.pm @@ -18,6 +18,7 @@ use parent qw/Catalyst::Controller NGCP::Panel::Role::API/; use NGCP::Panel::Utils::Journal qw(); use NGCP::Panel::Utils::License; +use NGCP::Panel::Utils::MetaRelease qw(get_meta_release_data); #with 'NGCP::Panel::Role::API'; @@ -408,6 +409,7 @@ sub platforminfo :Path('/api/platforminfo') :CaptureArgs(0) { $data->{multisite} = {}; } $data->{call_flow} = $c->config->{callflow} // {}; + $data->{release_info} = get_meta_release_data($c); } return to_json($data, {pretty => 1, canonical => 1}); diff --git a/lib/NGCP/Panel/Utils/MetaRelease.pm b/lib/NGCP/Panel/Utils/MetaRelease.pm new file mode 100644 index 0000000000..ef632610ec --- /dev/null +++ b/lib/NGCP/Panel/Utils/MetaRelease.pm @@ -0,0 +1,196 @@ +package NGCP::Panel::Utils::MetaRelease; +use strict; +use warnings; +use Exporter; +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + +$VERSION = 1.00; +@ISA = qw(Exporter); +@EXPORT = (); +@EXPORT_OK = qw(get_meta_release_data); +%EXPORT_TAGS = ( + DEFAULT => [qw(&get_meta_release_data)], + all => [qw(&get_meta_release_data)], +); + +use English; + +use Config::Tiny; +use DateTime::Format::Strptime; +use Digest::MD5 qw(md5_hex); +use JSON qw(); +use LWP::UserAgent; + +my $meta_release_url = 'https://deb.sipwise.com/meta-release'; +my $cache_ttl = 3600; +my $cache_key = 'meta_release'; + +my $ngcp_version_file = '/etc/ngcp_version'; +my $ngcp_roles_file = '/etc/default/ngcp-roles'; + +my $date_parser = DateTime::Format::Strptime->new( + pattern => '%a, %d %B %Y, %H:%M:%S %Z', + time_zone => 'UTC', +); + +sub read_ngcp_version { + my $c = shift; + + open(my $fh, '<', $ngcp_version_file) or do { + $c->log->debug("Cannot read file: $ngcp_version_file: $!"); + return; + }; + + my $version = <$fh>; + + return unless $version; + + chomp($version); + close $fh; + + return $version; +} + +sub read_ngcp_roles { + my $c = shift; + + my $root = Config::Tiny->read($ngcp_roles_file, 'utf8'); + + if (my $err = $Config::Tiny::errstr) { + $c->log->debug("Error when reading $ngcp_roles_file: $err"); + return; + } + + unless ($root->{_}) { + $c->log->debug("Unexpected result from reading $ngcp_roles_file"); + return; + } + + map { $root->{_}{$_} =~ s/(^['"]|['"]$)//g } keys %{$root->{_}}; + + return $root->{_};; +} + +sub read_meta_release_content { + my ($c, $content) = @_; + + my $meta_release = Config::Tiny->read_string($content, 'utf8'); + + if (my $err = $Config::Tiny::errstr) { + $c->log->debug("Error when reading meta release content: $err"); + return; + } + + my $parsed = {}; + + my $ngcp_version = read_ngcp_version($c); + my $ngcp_roles = read_ngcp_roles($c); + + return unless $ngcp_version; + return unless $ngcp_roles; + + my $latest = $meta_release->{latest}->{Release} // ''; + my $latest_lts = $meta_release->{latest_lts}->{Release} // ''; + + foreach my $key (keys %{$meta_release}) { + my $data = $meta_release->{$key}; + next unless $key eq $ngcp_version || + $key eq 'latest' || + $key eq 'latest_lts' || + $key eq $latest || + $key eq $latest_lts; + foreach my $sub (qw/Dist LTS Supported Expired Release/) { + next unless exists $data->{$sub}; + my $ch_key = lc($sub); + my $value = $data->{$sub}; + if ($ch_key eq 'lts' or $ch_key eq 'supported') { + $value = ($value eq "1" or $value == 1) ? JSON::true : JSON::false; + } + $parsed->{$key}{$ch_key} = $value; + } + if ($data->{Expired}) { + my $expires = $date_parser->parse_datetime($data->{Expired}); + $parsed->{$key}{expired} = $expires->epoch <= time ? JSON::true : JSON::false; + } + if ($data->{CE_Expired}) { + my $ce_expires = $date_parser->parse_datetime($data->{CE_Expired}); + $parsed->{$key}{ce_expired} = $ce_expires->epoch <= time ? JSON::true : JSON::false; + } + foreach my $sub (qw/Date Expired CE_Expired/) { + if ($data->{$sub}) { + my $ch_key = $sub; + $ch_key = $sub eq 'Expired' ? 'expires' : lc($ch_key); + $ch_key = $sub eq 'CE_Expired' ? 'ce_expires' : lc($ch_key); + my $date = $date_parser->parse_datetime($data->{$sub}); + my $iso8601 = $date->strftime('%Y-%m-%dT%H:%M:%SZ'); + $parsed->{$key}{$ch_key} = $iso8601; + $parsed->{$key}{$ch_key . '_timestamp'} = $date->epoch; + delete $parsed->{$key}{$sub}; + } + } + } + + $parsed->{this} = { + release => $ngcp_version, + type => $ngcp_roles->{NGCP_TYPE}, + available_types => [qw(spce sppro carrier)], + }; + + return $parsed; +} + +sub fetch_meta_release_data { + my $c = shift; + + my $cache = $c->cache->get($cache_key) || {}; + my $now = time; + my $last_fetch_time = $cache->{last_fetch_time} // 0; + + my $expired = $last_fetch_time ? ($now - $last_fetch_time < $cache_ttl) : 1; + if ($cache->{parsed} && !$expired) { + return (1, $cache->{parsed}); + } + + my $ua = LWP::UserAgent->new( + timeout => 10, + ssl_opts => { + verify_hostname => 0, + }, + ); + + my $response = $ua->get($meta_release_url); + + if ($response->code != 200) { + $c->log->debug("Failed to fetch $meta_release_url " . $response->status_line); + return (0, undef); + } + + my $content = $response->content; + my $md5sum = md5_hex($content); + + if (!$cache->{md5sum} || $cache->{md5sum} ne $md5sum) { + $cache->{content} = $content; + $cache->{md5sum} = $md5sum; + } + + my $parsed = read_meta_release_content($c, $content); + + $cache->{last_fetch_time} = $now; + $cache->{parsed} = $parsed; + + $c->cache->set($cache_key, $cache); + + return (1, $parsed); +} + +sub get_meta_release_data { + my $c = shift; + + my ($res, $data) = fetch_meta_release_data($c); + + return $res && $data ? $data : {}; +} + +1; + +# vim: set tabstop=4 expandtab: