App-FuguVM
view release on metacpan or search on metacpan
lib/App/FuguVM/DiskCache.pm view on Meta::CPAN
# Publish $disk_path as the cached base image for $key. The
# method builds the entry whole in a sibling temporary directory.
# Then it publishes the entry with a rename of that directory.
# Thus no reader sees the base image of one installation beside
# the metadata of another installation. Such a mismatch would
# look live. It would then wedge every later boot with a root
# password that does not open the image.
#
# Entries are write-once: a rename onto a populated directory
# fails with ENOTEMPTY, and the existing entry wins. Return the
# base image path, or undef on any failure.
sub store ( $self, $key, $disk_path, $meta = {} )
{
return if !defined $key;
if ( !-f $disk_path ) {
warn "Cannot cache missing disk image: $disk_path\n";
return;
}
Fugu::File->ensure_dir( $self->installed_dir ) or return;
my $target = $self->entry_dir($key);
if ( -e "$target/" . BASE_NAME ) {
Fugu::Log->default->warning(
'Image cache entry already exists: %s', $key );
return;
}
my $built = Fugu::File->atomic_dir(
$target,
sub ($tmp) {
my $base = "$tmp/" . BASE_NAME;
return 0
if !App::FuguVM::Disk->convert( $disk_path, $base );
chmod 0400, $base or do {
Fugu::Log->default->warning(
'Cannot set permissions on %s: %s',
$base, $! );
return 0;
};
my %record = (
%$meta,
key => $key,
created_at => time,
);
# The record carries the guest root password, so
# the file gets its mode before its content
return Fugu::File->write_json( "$tmp/" . META_NAME,
\%record, mode => 0600 ) ? 1 : 0;
} );
return if !defined $built;
return "$target/" . BASE_NAME;
}
# $self->lock_entry($key, $timeout):
# Return an open, exclusively locked handle on the lock file of
# $key. The caller holds the lock until the handle closes or the
# process exits, so a stale lock file blocks nothing. Return
# undef when the deadline elapses, and undef when the file cannot
# open.
#
# The lock serializes the first population of one entry across
# every project that shares the cache directory. It does not
# replace the write-once rule of store: a run that lost the lock
# to the deadline still cannot publish a second entry.
#
# The file name starts with a dot, so it cannot collide with an
# entry. list reads only a directory whose name has no leading
# dot. sweep_temp removes only a '.tmp.' directory.
sub lock_entry ( $self, $key, $timeout = LOCK_TIMEOUT )
{
return if !defined $key;
Fugu::File->ensure_dir( $self->installed_dir ) or return;
my $path = $self->installed_dir . '/' . LOCK_PREFIX . $key;
open my $fh, '>>', $path or do {
Fugu::Log->default->warning( 'Cannot open %s: %s', $path, $! );
return;
};
my $locked =
Fugu::Timeout::bounded( $timeout, sub { flock $fh, LOCK_EX } );
return $fh if $locked;
close $fh;
return;
}
# $self->list:
# Return every complete entry, newest first, as
# { key, dir, base, size, created_at, meta, snapshots }
sub list ($self)
{
my @entries;
my $installed = $self->installed_dir;
return \@entries if !-d $installed;
opendir my $dh, $installed or return \@entries;
my @keys = grep { !/^\./ && -d "$installed/$_" } readdir $dh;
closedir $dh;
for my $key ( sort @keys ) {
my $entry = $self->lookup($key) or next;
$entry->{size} = Fugu::Proxy::Cache->dir_size( $entry->{dir} );
$entry->{created_at} = $entry->{meta}{created_at};
$entry->{snapshots} = $self->_snapshot_names($key);
push @entries, $entry;
}
return [
sort { ( $b->{created_at} // 0 ) <=> ( $a->{created_at} // 0 ) }
@entries
];
}
# $self->key_for_path($path):
# Return the cache key whose entry contains $path. The path can
# point to a base image or to a snapshot. Return undef when $path
# lies outside the cache. The method lets a caller answer "which
# cached image is this disk built on?".
sub key_for_path ( $self, $path )
{
return if !defined $path;
my $installed = $self->installed_dir . '/';
return if index( $path, $installed ) != 0;
my ($key) = split m{/}, substr( $path, length $installed ), 2;
return if !defined $key || $key eq '';
return $key;
}
# $self->snapshot_dir($key):
# Return the directory that holds the named snapshot layers of
# an entry.
sub snapshot_dir ( $self, $key )
{
return $self->entry_dir($key) . '/' . SNAPSHOT_DIR;
}
# $self->snapshot_path($key, $name):
# Return the absolute path of a named snapshot. The method does
( run in 1.720 second using v1.01-cache-2.11-cpan-800906f7e73 )