You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-panel/t/lib/NGCP/Test/ReferenceData.pm

377 lines
12 KiB

package NGCP::Test::ReferenceData;
use strict;
use warnings;
use Moose;
use File::Basename;
use File::Slurp;
use File::Find::Rule;
use File::Path qw/make_path/;
use JSON qw/from_json to_json/;
use Data::Dumper;
has 'sid' => (
isa => 'Maybe[Str]',
is => 'rw',
lazy => 1,
default => sub { my $self = shift; $self->_test->generate_sid(); },
);
has 'use_persistent' => (
isa => 'Int',
is => 'rw',
default => 1,
);
has 'persistent_path' => (
isa => 'Str',
is => 'rw',
default => '/tmp',
);
has 'client' => (
isa => 'Object',
is => 'ro',
);
has 'ref_dir' => (
is => 'rw',
isa => 'Str',
default => 'ReferenceData',
);
has 'depends' => (
is => 'rw',
isa => 'ArrayRef',
default => sub {[]},
);
has '_dependstree' => (
is => 'rw',
isa => 'HashRef',
default => sub {{}},
);
has '_refdata' => (
is => 'rw',
isa => 'ArrayRef',
default => sub {[]},
);
has '_reftree' => (
is => 'rw',
isa => 'HashRef',
default => sub {{}},
);
has '_refurls' => (
is => 'rw',
isa => 'ArrayRef',
default => sub {[]},
);
has 'delete_persistent' => (
isa => 'Bool',
is => 'rw',
default => 0,
);
has '_test' => (
isa => 'Object',
is => 'ro',
);
sub BUILD {
my ($self) = @_;
my $ref_done = 0;
unless(defined $self->sid) {
$self->sid("".time);
} else {
if($self->use_persistent) {
my $data_path = $self->persistent_path . "/" . $self->sid . "/refdata.json";
my $tree_path = $self->persistent_path . "/" . $self->sid . "/reftree.json";
if(-r $data_path && -r $tree_path) {
#$self->_test->debug("Persistent file $data_path exists, reading from there\n");
my $json = read_file($data_path);
my $data = from_json($json);
$self->_refdata($data);
#$self->_test->debug("Persistent file $tree_path exists, reading from there\n");
$json = read_file($tree_path);
$data = from_json($json);
$self->_reftree($data);
$ref_done = 1;
}
}
}
unless($ref_done) {
$self->_read_refdata();
# if we read it from file, write it back to cache if enabled
if($self->use_persistent) {
my $path = $self->persistent_path . "/" . $self->sid;
#$self->_test->debug("Checking for persistent path '$path'\n");
if(-d $path) {
#$self->_test->debug("Persistent path $path already exists, skip writing data\n");
} else {
#$self->_test->debug("Creating persistent path '$path'\n");
make_path $path or die "Failed to create persistent path $path: $!\n";
my $json = to_json($self->_refdata);
my $fpath = "$path/refdata.json";
open my $fh, ">", $fpath or die "Failed to open persistent file $fpath for writing: $!\n";
print $fh $json;
close $fh;
$json = to_json($self->_reftree);
$fpath = "$path/reftree.json";
open $fh, ">", $fpath or die "Failed to open persistent file $fpath for writing: $!\n";
print $fh $json;
close $fh;
}
} else {
#$self->_test->debug("Persistent feature disabled\n");
}
}
$self->_read_depends();
}
sub _read_refdata {
my ($self) = @_;
my ($name,$path,$suffix) = fileparse(__FILE__);
$path .= $self->ref_dir;
my @files = File::Find::Rule->file()
->name('*.json')
->in(($path));
my @json = ();
foreach my $file(@files) {
my $json = read_file($file)
or die "Failed to open reference file '$file': $!\n";
my $data= from_json($json);
if(ref $data eq "ARRAY") {
push @json, @{ $data };
} elsif(ref $data eq "HASH") {
push @json, $data;
} else {
die "Reference json file '$file' must contain array or object\n";
}
}
$self->_refdata(\@json);
foreach my $ref(@{ $self->_refdata }) {
$self->_resolve_deps($ref->{name}, $ref);
}
#$self->_test->debug Dumper $self->_reftree;
}
sub _read_depends {
my ($self) = @_;
foreach my $d(@{ $self->depends }) {
my $resource_found = 0;
foreach my $k(keys %{ $self->_reftree }) {
my $ref = $self->_reftree->{$k};
#$self->_test->debug("Checking dependency '$$d{resource}' against reference '$$ref{name}' of type '$$ref{type}'\n");
if($ref->{type} eq $d->{resource}) {
#$self->_test->debug("Found a reference data entry of resource $$d{resource}\n");
if(defined $d->{hints}) {
my $hint_found = 0;
foreach my $hint(@{ $d->{hints} }) {
#$self->_test->debug("Checking hint field $$hint{field} with expected value $$hint{value} against ".$ref->{data}->{$hint->{field}}."\n");
if(defined $hint->{name} && $hint->{name} eq $ref->{name}) {
$hint_found = 1;
$self->_dependstree->{$d->{name}}->{data} = $ref->{data};
$self->_dependstree->{$d->{name}}->{meta}->{type} = $ref->{type};
last;
} elsif(defined $hint->{field} && exists $ref->{data}->{$hint->{field}} &&
"".$hint->{value} eq "".$ref->{data}->{$hint->{field}}) {
#$self->_test->debug("found reference data using hints:\n");
#$self->_test->debug Dumper $ref->{data};
$hint_found = 1;
$self->_dependstree->{$d->{name}}->{data} = $ref->{data};
$self->_dependstree->{$d->{name}}->{meta}->{type} = $ref->{type};
last;
}
}
if($hint_found) {
$resource_found = 1;
last;
}
} else {
#$self->_test->debug("found reference data:\n");
#$self->_test->debug Dumper $ref->{data};
$resource_found = 1;
$self->_dependstree->{$d->{name}}->{data} = $ref->{data};
$self->_dependstree->{$d->{name}}->{meta}->{type} = $ref->{type};
last;
}
}
}
unless($resource_found) {
die "No reference data for '$$d{resource}' found with given hints\n";
}
}
}
sub _replace_vars {
my ($self, $var, $refname) = @_;
if(ref $$var eq "HASH") {
foreach my $k(keys %{ $$var }) {
$self->_replace_vars(\$$var->{$k}, $refname);
}
} elsif(ref $$var eq "ARRAY") {
foreach my $k(@{ $$var }) {
$self->_replace_vars(\$k, $refname);
}
} elsif(ref $$var ne "") {
return;
} else {
my @vars = $$var =~ /(\$\{.+?\})/g;
$self->_test->debug("Found vars in $refname:\n");
$self->_test->debug(Dumper \@vars);
foreach my $tvar(@vars) {
my $val;
my $revar = $tvar;
$revar =~ s/([\$\{\}])/\\$1/g;
$self->_test->debug("Replaced varname '$tvar' by revar '$revar'\n");
if($tvar eq '${sid}') {
$val = $self->sid;
$self->_test->debug("Replace $tvar by sid $val\n");
} else {
my $len = length($tvar) - 3;
my $varname = substr($tvar, 2, $len);
$self->_test->debug("Extracted varname '$varname'\n");
unless(exists $self->_reftree->{$varname}) {
die "Internal error, unresolved dependency '$varname'\n";
}
$val = $self->_reftree->{$varname}->{data}->{id};
}
$self->_test->debug("about to replace, before='$$var'\n");
$$var =~ s/$revar/$val/g;
$self->_test->debug("done replace, after='$$var'\n");
}
}
}
sub _resolve_deps {
my ($self, $refname, $ref) = @_;
#$self->_test->debug("---- entering _resolve_deps with refname='$refname' and ref=\n");
#$self->_test->debug Dumper $ref;
if(defined $ref) {
if(exists $ref->{depends}) {
foreach my $dep(@{ $ref->{depends} }) {
#$self->_test->debug("found dependency '$dep' on ref '$refname'\n");
my $d = $self->_resolve_deps($dep);
unless($d) {
die "Unresolved dependency '$dep' on '$refname'\n";
}
$self->_resolve_deps($dep, $d);
}
$self->_replace_vars(\$ref->{data}, $refname);
unless($ref->{data}->{id}) {
#$self->_test->debug("The '$refname' has no id yet, create via API, uri is 'api/$$ref{type}\n");
#$self->_test->debug Dumper $ref->{data};
my $url = 'api/'.$ref->{type};
my $res = $self->client->_post('api/'.$ref->{type}, $ref->{data});
unless($res->is_success) {
die "Failed to create $refname: ".$res->status_line."\n";
}
my $id = $res->header('Location');
$id =~ s/^(.+\/)([^\/]+)$/$2/;
$ref->{data}->{id} = $id; # TODO: refetch if no content?
$url .= "/$id";
$self->_reftree->{$refname} = $ref;
push @{ $self->_refurls }, $url;
}
return $ref;
}
} else {
if(exists $self->_reftree->{$refname}) {
#$self->_test->debug("Found ref '$refname' in tree, just return it\n");
return $self->_reftree->{$refname};
} else {
foreach my $ref(@{ $self->_refdata }) {
if($ref->{name} eq $refname) {
#$self->_test->debug("Found ref '$refname' in JSON, return for creation\n");
return $ref;
}
}
}
}
}
sub data {
my ($self, $name) = @_;
my $ref = $self->_dependstree->{$name};
if($ref) {
if($ref->{meta}->{full}) {
return $ref->{data};
} else {
$self->_test->debug("$name not fully fetched yet\n");
my $res = $self->client->_get(
'api/'.$ref->{meta}->{type}.'/'.$ref->{data}->{id}
);
unless($res->is_success) {
die "Failed to fully fetch " .
"$ref->{meta}->{type} #$ref->{data}->{id}: " .
$res->status_line."\n";
}
my $data = from_json($res->decoded_content);
$self->_dependstree->{$name}->{data} = $data;
$self->_test->debug("$name now fully fetched yet\n");
$self->_dependstree->{$name}->{meta}->{full} = 1;
return $data;
}
} else {
die "Failed to find given dependency name '$name' in dependency tree, check name against 'depends' in constructor\n";
}
}
sub DESTROY {
my ($self) = @_;
if($self->delete_persistent) {
while((my $url = pop @{ $self->_refurls })) {
#$self->_test->debug("Deleting $url\n");
my $res = $self->client->_delete($url);
unless($res->is_success) {
my $data = from_json($res->decoded_content);
if($res->code == 404) {
my $res = $self->client->_patch($url, [{
op => 'replace',
path => '/status',
value => 'terminated'
}]);
unless($res->is_success) {
$self->_test->info("Failed to both auto-delete or auto-terminate '$url': $$data{message}\n");
} else {
#$self->_test->debug("The $url successfully terminated\n");
}
}
} else {
#$self->_test->debug("The $url successfully deleted\n");
}
}
}
}
1;