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.
377 lines
12 KiB
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;
|