[Bps-public-commit] app-aws-cloudwatch-monitor branch 0.03/update-meta-data-caching-writes created. 542bcc0ce07c60029a544451870f96e2ab68ed65
BPS Git Server
git at git.bestpractical.com
Fri May 20 22:33:52 UTC 2022
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "app-aws-cloudwatch-monitor".
The branch, 0.03/update-meta-data-caching-writes has been created
at 542bcc0ce07c60029a544451870f96e2ab68ed65 (commit)
- Log -----------------------------------------------------------------
commit 542bcc0ce07c60029a544451870f96e2ab68ed65
Author: Blaine Motsinger <blaine at bestpractical.com>
Date: Fri May 20 11:33:19 2022 -0500
Add test dep for Test::Warnings
diff --git a/Makefile.PL b/Makefile.PL
index abd072b..9370602 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -29,6 +29,7 @@ WriteMakefile(
'File::Spec' => 0,
'Test::Exception' => '0.42', # recommended by Test2
'Test::More' => '0.98', # for subtest()
+ 'Test::Warnings' => 0,
},
PREREQ_PM => {
'base' => 0,
commit 98c1aeea9247a3ca060171c36be7efa3247f757e
Author: Blaine Motsinger <blaine at bestpractical.com>
Date: Fri May 20 11:22:44 2022 -0500
Add tests for get_meta_data
These tests verify the logic and return for get_meta_data
happens when expected:
- don't write cache if meta_data wasn't retrieved from mount
- get meta_data from mount when cache returns empty
- write the cache if meta_data was retrieved from mount
- warn if cache and mount both return no value
- use meta_data from cache if TTL is expired and mount returns empty
- warn if meta_data from cache is expired and mount returns empty
The cache read/write and GET requests to AWS are all mocked.
Integration testing to read/write the cache should happen in tests
for read_meta_data and write_meta_data.
diff --git a/t/cloudwatchclient-get_meta_data.t b/t/cloudwatchclient-get_meta_data.t
new file mode 100644
index 0000000..a01315f
--- /dev/null
+++ b/t/cloudwatchclient-get_meta_data.t
@@ -0,0 +1,124 @@
+use strict;
+use warnings;
+
+use FindBin ();
+use lib "$FindBin::RealBin/../lib", "$FindBin::RealBin/lib";
+use App::AWS::CloudWatch::Monitor::Test;
+use Test::Warnings qw{:no_end_test};
+
+my $class = 'App::AWS::CloudWatch::Monitor::CloudWatchClient';
+use_ok($class);
+
+use constant {
+ DO_NOT_CACHE => 0,
+ USE_CACHE => 1,
+};
+
+my %meta_data_cache = (
+ '/instance-id' => 'i12345testcache',
+);
+
+my %meta_data_mount = (
+ '/instance-id' => 'i12345testmount',
+);
+
+my $return_expired_ttl = 0;
+my $return_empty_cache = 0;
+App::AWS::CloudWatch::Monitor::Test::override(
+ package => 'App::AWS::CloudWatch::Monitor::CloudWatchClient',
+ name => 'read_meta_data',
+ subref => sub {
+ my $resource = shift;
+ my $default_ttl = shift;
+ my $meta_data;
+ unless ($return_empty_cache) {
+ $meta_data = $meta_data_cache{$resource};
+ }
+ return ( $return_expired_ttl, $meta_data );
+ },
+);
+
+my $meta_data_was_written = 0;
+App::AWS::CloudWatch::Monitor::Test::override(
+ package => 'App::AWS::CloudWatch::Monitor::CloudWatchClient',
+ name => 'write_meta_data',
+ subref => sub {
+ my $resource = shift;
+ my $data_value = shift;
+ $meta_data_was_written = 1;
+ return;
+ },
+);
+
+my $return_empty_mount = 0;
+App::AWS::CloudWatch::Monitor::Test::override(
+ package => 'App::AWS::CloudWatch::Monitor::CloudWatchClient',
+ name => 'get', # get is imported from LWP::Simple
+ subref => sub {
+ my $uri = shift;
+ my $resource;
+ if ( $uri =~ /.latest\/meta-data(\/.+)/ ) {
+ $resource = $1;
+ }
+ my $meta_data;
+ unless ($return_empty_mount) {
+ $meta_data = $meta_data_mount{$resource};
+ }
+ return $meta_data;
+ },
+);
+
+note('get meta_data from cache');
+my $instance_id = App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', USE_CACHE );
+is( $instance_id, $meta_data_cache{'/instance-id'}, 'instance-id from cache returned expected' );
+is( $meta_data_was_written, 0, 'meta_data was not written' );
+reset_vars();
+
+note('get meta_data from mount');
+$return_empty_cache = 1;
+note("don't write the cache");
+$instance_id = App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', DO_NOT_CACHE );
+is( $instance_id, $meta_data_mount{'/instance-id'}, 'instance-id from mount returned expected' );
+is( $meta_data_was_written, 0, 'meta_data was not written' );
+note("write the cache");
+$instance_id = App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', USE_CACHE );
+is( $instance_id, $meta_data_mount{'/instance-id'}, 'instance-id from mount returned expected' );
+is( $meta_data_was_written, 1, 'meta_data was written' );
+reset_vars();
+
+note("return empty from cache and mount");
+$return_empty_cache = 1;
+$return_empty_mount = 1;
+$instance_id = App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', USE_CACHE );
+is( $instance_id, undef, 'instance-id from mount returned empty' );
+is( $meta_data_was_written, 0, 'meta_data was not written' );
+my $warning = Test::Warnings::warning { App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', USE_CACHE ) };
+like(
+ $warning,
+ qr/^meta-data resource \/instance-id returned empty/,
+ 'warning is generated if cache and mount return no meta-data',
+);
+reset_vars();
+
+note("return expired from cache and empty mount");
+$return_expired_ttl = 1;
+$return_empty_mount = 1;
+$instance_id = App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', USE_CACHE );
+is( $instance_id, $meta_data_cache{'/instance-id'}, 'instance-id from cache returned expected' );
+is( $meta_data_was_written, 0, 'meta_data was not written' );
+$warning = Test::Warnings::warning { App::AWS::CloudWatch::Monitor::CloudWatchClient::get_meta_data( '/instance-id', USE_CACHE ) };
+like(
+ $warning,
+ qr/^meta-data resource \/instance-id cache TTL is expired and the meta-data mount failed to return data\.\nexpired data from the cache will continue to be used and will persist in the cache until the meta-data mount starts returning data again\./,
+ 'warning is generated if cache is expired and mount return no meta-data',
+);
+reset_vars();
+
+done_testing();
+
+sub reset_vars {
+ $return_expired_ttl = 0;
+ $return_empty_cache = 0;
+ $meta_data_was_written = 0;
+ $return_empty_mount = 0;
+}
commit 3046397508ca0ded3dd22854b4b11016d75ba8fd
Author: Blaine Motsinger <blaine at bestpractical.com>
Date: Thu May 19 13:30:35 2022 -0500
Fix meta-data caching writes
The meta-data cache files are written after every call, regardless
of the TTL variables. This presents a couple issues.
First, the age of the cache files never get older than the time of
the last call, effectively making the TTL time meaningless. This
leads to far too many unnecessary writes to the disk since the
cache files never age.
Second, since the TTL time is not honored, if the special meta-data
mount is incorrectly returning empty string (an issue at AWS or the
mount on the instance), the benefit of relying on a cached value is
lost.
This commit fixes the meta-data cache writes to now only write the
cache file if a value was retrieved from the special mount, which
only happens if the TTL is reached or the cache file doesn't exist
or doesn't contain a value.
Additionally, if the TTL of the cache has expired and the special
mount didn't return data, the expired value from the cache will now
continue to be used until the mount starts to return data again.
New warnings have also been added to inform the admin that expired
data is being used, and that no data was found in either the cache
or mount.
diff --git a/lib/App/AWS/CloudWatch/Monitor/CloudWatchClient.pm b/lib/App/AWS/CloudWatch/Monitor/CloudWatchClient.pm
index 21c8845..cc65d1e 100644
--- a/lib/App/AWS/CloudWatch/Monitor/CloudWatchClient.pm
+++ b/lib/App/AWS/CloudWatch/Monitor/CloudWatchClient.pm
@@ -122,26 +122,46 @@ Queries meta data for the current EC2 instance.
sub get_meta_data {
my $resource = shift;
my $use_cache = shift;
- my $meta_data = read_meta_data( $resource, $meta_data_short_ttl );
+ my ( $ttl_expired, $cache_data ) = read_meta_data( $resource, $meta_data_short_ttl );
- my $base_uri = 'http://169.254.169.254/latest/meta-data';
- my $data_value = !$meta_data ? get $base_uri. $resource : $meta_data;
+ if ( $ttl_expired || !$cache_data ) {
+ my $base_uri = 'http://169.254.169.254/latest/meta-data';
+ my $mount_data = get $base_uri . $resource;
- if ( !$data_value ) {
- return "";
+ if ($mount_data) {
+ $cache_data = $mount_data;
+
+ if ($use_cache) {
+ write_meta_data( $resource, $mount_data );
+ }
+ }
+ # although the likelihood is low, there may be circumstances where the TTL of the cache
+ # has expired and the meta-data mount returned no data. in that case, use the expired
+ # cache data, but warn that expired data is being used. once the meta-data mount starts
+ # to return data again, the cache will be updated and TTL countdown started over.
+ else {
+ if ( $ttl_expired && $cache_data ) {
+ warn "meta-data resource $resource cache TTL is expired and the meta-data mount failed to return data.\n" .
+ "expired data from the cache will continue to be used and will persist in the cache until the meta-data mount starts returning data again.\n";
+ }
+ }
}
- if ($use_cache) {
- write_meta_data( $resource, $data_value );
+ unless ($cache_data) {
+ warn "meta-data resource $resource returned empty\n";
}
- return $data_value;
+ return $cache_data;
}
=item read_meta_data
Reads meta-data from the local filesystem.
+Returns a list containing a truthy value indicating the ttl has expired, and the cache data value.
+
+ my ( $ttl_expired, $cache_data ) = read_meta_data( $resource, $meta_data_short_ttl );
+
=cut
sub read_meta_data {
@@ -156,27 +176,33 @@ sub read_meta_data {
$meta_data_ttl = $default_ttl if ( !defined($meta_data_ttl) );
my $data_value;
+ my $ttl_expired = 0;
if ($location) {
my $filename = $location . $resource;
if ( -d $filename ) {
$data_value = `/bin/ls $filename`;
}
elsif ( -e $filename ) {
+ my $ret = open( my $file_fh, '<', $filename );
+ unless ($ret) {
+ warn "open: unable to read meta data from filesystem: $!\n";
+ return;
+ }
+ while ( my $line = <$file_fh> ) {
+ $data_value .= $line;
+ }
+ close $file_fh;
+
my $updated = ( stat($filename) )[9];
my $file_age = time() - $updated;
- if ( $file_age < $meta_data_ttl ) {
- open( my $file_fh, '<', $filename )
- or warn "open: unable to read meta data from filesystem: $!\n";
- while ( my $line = <$file_fh> ) {
- $data_value .= $line;
- }
- close $file_fh;
+ if ( $file_age >= $meta_data_ttl ) {
+ $ttl_expired = 1;
}
}
}
chomp($data_value) if $data_value;
- return $data_value;
+ return ( $ttl_expired, $data_value );
}
=item write_meta_data
-----------------------------------------------------------------------
hooks/post-receive
--
app-aws-cloudwatch-monitor
More information about the Bps-public-commit
mailing list