Skip to content
This repository has been archived by the owner on Jul 24, 2021. It is now read-only.

Commit

Permalink
new endpoint POST /device_report to test a report without a device
Browse files Browse the repository at this point in the history
closes #609.
  • Loading branch information
karenetheridge committed Apr 3, 2019
1 parent 9d2fc03 commit f5f6c24
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 1 deletion.
17 changes: 17 additions & 0 deletions json-schema/response.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,23 @@ definitions:
$ref: /definitions/uuid
device_report_id:
$ref: /definitions/uuid
ReportValidationResults:
type: object
additionalProperties: false
required:
- device_id
- validation_plan_id
- status
- results
properties:
device_id:
$ref: /definitions/device_id
validation_plan_id:
$ref: /definitions/uuid
status:
$ref: /definitions/validation_status
results:
$ref: /definitions/ValidationResults
WorkspaceRelays:
type: array
uniqueItems: true
Expand Down
69 changes: 68 additions & 1 deletion lib/Conch/Controller/DeviceReport.pm
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Response uses the ValidationStateWithResults json schema.
sub process ($c) {
my $unserialized_report = $c->validate_input('DeviceReport');
if (not $unserialized_report) {
$c->log->debug('Device report input failed validation');
$c->log->debug('Device report input did not match json schema specification');

if (not $c->db_devices->active->search({ id => $c->stash('device_id') })->exists) {
$c->log->debug('Device id '.$c->stash('device_id').' does not exist; cannot store bad report');
Expand Down Expand Up @@ -137,6 +137,7 @@ sub process ($c) {
device => $c->db_ro_devices->find($device->id),
device_report => $device_report,
);
return $c->status(400, { error => 'no validations ran' }) if not $validation_state;
$c->log->debug("Validations ran with result: ".$validation_state->status);

# calculate the device health based on the validation results.
Expand Down Expand Up @@ -407,6 +408,72 @@ sub get ($c) {
return $c->status(200, $c->stash('device_report_rs')->single);
}

=head2 validate_report
Process a device report without writing anything to the database; otherwise behaves like
L</process>. The described device does not have to exist.
Response uses the ReportValidationResults json schema.
=cut

sub validate_report ($c) {
my $unserialized_report = $c->validate_input('DeviceReport');
if (not $unserialized_report) {
$c->log->debug('Device report input did not match json schema specification');
return;
}

my $hw = $c->_get_hardware_product($unserialized_report);
return $c->status(409, { error => 'Could not locate hardware product' }) if not $hw;
return $c->status(409, { error => 'Hardware product does not contain a profile' })
if not $hw->hardware_product_profile;

my $validation_plan = $c->_get_validation_plan($unserialized_report);
return $c->status(500, { error => 'failed to find validation plan' }) if not $validation_plan;
$c->log->debug('Running validation plan '.$validation_plan->id.': '.$validation_plan->name.'"');

my ($status, @validation_results);
$c->txn_wrapper(sub ($c) {
my $device = $c->db_devices->update_or_create({
id => $unserialized_report->{serial_number},
system_uuid => $unserialized_report->{system_uuid},
hardware_product_id => $hw->id,
state => $unserialized_report->{state},
health => 'unknown',
last_seen => \'now()',
uptime_since => $unserialized_report->{uptime_since},
hostname => $unserialized_report->{os}{hostname},
updated => \'now()',
deactivated => undef,
});

# we do not call _record_device_configuration, because no validations
# should be using that information, instead choosing to respect the report data.

($status, @validation_results) = Conch::ValidationSystem->new(
schema => $c->ro_schema,
log => $c->log,
)->run_validation_plan(
validation_plan => $validation_plan,
device => $device,
data => $unserialized_report,
no_save_db => 1,
);

die 'rollback: device used for report validation should not be persisted';
});

return $c->status(400, { error => 'no validations ran' }) if not @validation_results;

$c->status(200, {
device_id => $unserialized_report->{serial_number},
validation_plan_id => $validation_plan->id,
status => $status,
results => \@validation_results,
});
}

=head2 _get_hardware_product
Find the hardware product for the device referenced by the report.
Expand Down
3 changes: 3 additions & 0 deletions lib/Conch/Route/DeviceReport.pm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Conch::Route::DeviceReport
Sets up the routes for /device_report:
POST /device_report
GET /device_report/:device_report_id
=cut
Expand All @@ -22,6 +23,8 @@ sub routes {
my $class = shift;
my $device_report = shift; # secured, under /device_report

$device_report->post('/')->to('device_report#validate_report');

# chainable action that extracts and looks up device_report_id from the path
# and device_id from the device_report
my $with_device_report = $device_report->under('/:device_report_id')
Expand Down
53 changes: 53 additions & 0 deletions t/integration/device-reports.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use v5.26;
use Mojo::Base -strict, -signatures;

use Test::More;
use Test::Warnings;
use Path::Tiny;
use Test::Deep;
use Test::Conch;

my $t = Test::Conch->new;

my $ro_user = $t->load_fixture('ro_user_global_workspace')->user_account;
$t->authenticate(user => $ro_user->email);

# matches report's product_name = Joyent-G1
$t->load_fixture('hardware_product_profile_compute');
my $hardware_product = $t->load_fixture('hardware_product_compute');

# create a validation plan with all current validations in it
Conch::ValidationSystem->new(log => $t->app->log, schema => $t->app->schema)->load_validations;
my @validations = $t->app->db_validations->all;
my ($validation_plan) = $t->load_validation_plans([{
name => 'Conch v1 Legacy Plan: Server',
description => 'Test Plan',
validations => [ map $_->module, @validations ],
}]);

subtest 'run report without an existing device' => sub {
my $report = path('t/integration/resource/passing-device-report.json')->slurp_utf8;
$t->post_ok('/device_report', { 'Content-Type' => 'application/json' }, $report)
->status_is(200)
->json_schema_is('ReportValidationResults')
->json_cmp_deeply({
device_id => 'TEST',
validation_plan_id => $validation_plan->id,
status => any(qw(error fail pass)), # likely some validations will hate this report.
# validations each produce one or more results
results => array_each(any(map +{
id => undef,
validation_id => $_->id,
category => $_->module->category,
component_id => ignore,
device_id => 'TEST',
hardware_product_id => $hardware_product->id,
hint => ignore,
message => ignore,
order => ignore,
status => any(qw(error fail pass)),
}, @validations)),
});
};

done_testing;

0 comments on commit f5f6c24

Please sign in to comment.