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

Commit

Permalink
Merge pull request #878 from joyent/ether/v3-overwrite-rack_layout
Browse files Browse the repository at this point in the history
new endpoint for (re)defining a rack of layouts at once
  • Loading branch information
karenetheridge authored Sep 10, 2019
2 parents c39475d + 59d7ff3 commit 044a3a6
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 5 deletions.
7 changes: 6 additions & 1 deletion docs/modules/Conch::Controller::Rack.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ Get all racks

Response uses the Racks json schema.

## layouts
## get\_layouts

Gets all the layouts for the specified rack.

Response uses the RackLayouts json schema.

## overwrite\_layouts

Given the layout definitions for an entire rack, removes all existing layouts that are not in
the new definition, as well as removing any device\_location assignments in those layouts.

## update

Update an existing rack.
Expand Down
6 changes: 6 additions & 0 deletions docs/modules/Conch::Route::Rack.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Unless otherwise noted, all routes require authentication.
- User requires the read-only role on a workspace that contains the rack
- Response: response.yaml#/RackLayouts

### `POST /rack/:rack_id/layouts`

- User requires the read/write role on a workspace that contains the rack
- Request: request.yaml#/RackLayouts
- Response: Redirect to the rack's layouts

### `GET /rack/:rack_id/assignment`

- User requires the read-only role on a workspace that contains the rack
Expand Down
14 changes: 14 additions & 0 deletions json-schema/request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,20 @@ definitions:
$ref: common.yaml#/definitions/uuid
rack_unit_start:
$ref: common.yaml#/definitions/positive_integer
RackLayouts:
type: array
uniqueItems: true
items:
type: object
additionalProperties: false
required:
- hardware_product_id
- rack_unit_start
properties:
hardware_product_id:
$ref: common.yaml#/definitions/uuid
rack_unit_start:
$ref: common.yaml#/definitions/positive_integer
RackLayoutUpdate:
type: object
additionalProperties: false
Expand Down
87 changes: 84 additions & 3 deletions lib/Conch/Controller/Rack.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package Conch::Controller::Rack;

use Mojo::Base 'Mojolicious::Controller', -signatures;

use List::Util qw(any none first uniq);
use List::Util qw(any none first uniq max);

=pod
Expand Down Expand Up @@ -97,15 +97,15 @@ sub get_all ($c) {
$c->status(200, \@racks);
}

=head2 layouts
=head2 get_layouts
Gets all the layouts for the specified rack.
Response uses the RackLayouts json schema.
=cut

sub layouts ($c) {
sub get_layouts ($c) {
my @layouts = $c->stash('rack_rs')
->related_resultset('rack_layouts')
->with_rack_unit_size
Expand All @@ -116,6 +116,87 @@ sub layouts ($c) {
$c->status(200, \@layouts);
}

=head2 overwrite_layouts
Given the layout definitions for an entire rack, removes all existing layouts that are not in
the new definition, as well as removing any device_location assignments in those layouts.
=cut

sub overwrite_layouts ($c) {
my $input = $c->validate_request('RackLayouts');
return if not $input;

my %layout_sizes = map +($_->{id} => $_->{rack_unit_size}),
$c->db_hardware_products->active->search({ id => { -in => [ map $_->{hardware_product_id}, $input->@* ] } })
->columns([qw(id rack_unit_size)])
->hri->all;

my %desired_slots; # map of all slots that will be occupied (slot => rack_unit_start)
foreach my $layout ($input->@*) {
my $size = $layout_sizes{$layout->{hardware_product_id}};
return $c->status(409, { error => 'hardware_product_id '.$layout->{hardware_product_id}.' does not exist' }) if not $size;
my @slots = $layout->{rack_unit_start} .. $layout->{rack_unit_start} + $size - 1;
my @overlaps = grep defined, map $desired_slots{$_}, @slots;
return $c->status(409, { error => 'layouts starting at rack_units '.$overlaps[0].' and '.$layout->{rack_unit_start}.' overlap' }) if @overlaps;
$desired_slots{$_} = $layout->{rack_unit_start} foreach @slots;
}

if (my $last_slot = max(keys %desired_slots)) {
return $c->status(409, { error => 'layout starting at rack_unit '.$desired_slots{$last_slot}.' will extend beyond the end of the rack' })
if $last_slot > $c->stash('rack_rs')->related_resultset('rack_role')->get_column('rack_size')->single;
}

my @existing_layouts = $c->stash('rack_rs')
->related_resultset('rack_layouts')
->columns([qw(hardware_product_id rack_unit_start)])
->hri->all;

my @layouts_to_delete = grep {
my $existing_layout = $_;
none {
$existing_layout->{hardware_product_id} eq $_->{hardware_product_id}
and $existing_layout->{rack_unit_start} eq $_->{rack_unit_start}
} $input->@*;
}
@existing_layouts;

my @layouts_to_create = grep {
my $new_layout = $_;
none {
$new_layout->{hardware_product_id} eq $_->{hardware_product_id}
and $new_layout->{rack_unit_start} eq $_->{rack_unit_start}
} @existing_layouts;
}
$input->@*;

$c->txn_wrapper(sub ($c) {
my $layouts_rs = $c->stash('rack_rs')
->search_related('rack_layouts', [ map +( +{
'rack_layouts.hardware_product_id' => $_->{hardware_product_id},
'rack_layouts.rack_unit_start' => $_->{rack_unit_start},
} ), @layouts_to_delete ] );

my $device_locations_rs = $layouts_rs->related_resultset('device_location');

my $deleted_device_locations = 0+$device_locations_rs->delete;
my $deleted_layouts = 0+$layouts_rs->delete;
$c->db_rack_layouts->populate([ map +{ rack_id => $c->stash('rack_id'), $_->%*, }, @layouts_to_create ]);

$c->log->debug(
join(', ',
($deleted_device_locations ? ('unlocated '.$deleted_device_locations.' devices') : ()),
($deleted_layouts ? ('deleted '.$deleted_layouts.' rack layouts') : ()),
(@layouts_to_create ? ('created '.scalar(@layouts_to_create).' rack layouts') : ()),
).' for rack '.$c->stash('rack_id'));
});

# if the result code was already set, we errored and rolled back the db...
return if $c->res->code;

$c->status(303, '/rack/'.$c->stash('rack_id').'/layouts');
}

=head2 update
Update an existing rack.
Expand Down
16 changes: 15 additions & 1 deletion lib/Conch/Route/Rack.pm
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ sub routes {
$with_rack->require_system_admin->delete('/')->to('#delete');

# GET /rack/:rack_id/layouts
$with_rack->get('/layouts')->to('#layouts');
$with_rack->get('/layouts')->to('#get_layouts');
# POST /rack/:rack_id/layouts
$with_rack->post('/layouts')->to('#overwrite_layouts');

# GET /rack/:rack_id/assignment
$with_rack->get('/assignment')->to('#get_assignment');
Expand Down Expand Up @@ -121,6 +123,18 @@ Unless otherwise noted, all routes require authentication.
=back
=head3 C<POST /rack/:rack_id/layouts>
=over 4
=item * User requires the read/write role on a workspace that contains the rack
=item * Request: request.yaml#/RackLayouts
=item * Response: Redirect to the rack's layouts
=back
=head3 C<GET /rack/:rack_id/assignment>
=over 4
Expand Down
92 changes: 92 additions & 0 deletions t/integration/crud/rack-layouts.t
Original file line number Diff line number Diff line change
Expand Up @@ -321,5 +321,97 @@ $t->delete_ok('/layout/'.$layout_3_6->id)
$t->get_ok('/layout/'.$layout_3_6->id)
->status_is(404);

# now we have these assigned slots:
# start 1, width 2
# start 11, width 4
# start 20, width 4 # occupied by 'my device'
# start 42, width 1

$t->post_ok('/rack/'.$rack_id.'/layouts',
json => [{ rack_unit_start => 1, hardware_product_id => create_uuid_str }])
->status_is(409)
->json_cmp_deeply({ error => re(qr/^hardware_product_id ${\Conch::UUID::UUID_FORMAT} does not exist$/) });

$t->post_ok('/rack/'.$rack_id.'/layouts',
json => [{ rack_unit_start => 42, hardware_product_id => $hw_product_compute->id }])
->status_is(409)
->json_is({ error => 'layout starting at rack_unit 42 will extend beyond the end of the rack' });

$t->post_ok('/rack/'.$rack_id.'/layouts',
json => [
{ rack_unit_start => 1, hardware_product_id => $hw_product_compute->id },
{ rack_unit_start => 2, hardware_product_id => $hw_product_compute->id },
])
->status_is(409)
->json_is({ error => 'layouts starting at rack_units 1 and 2 overlap' });

$t->post_ok('/rack/'.$rack_id.'/layouts',
json => [
# unchanged
{ rack_unit_start => 1, hardware_product_id => $hw_product_compute->id },
# unchanged, and has a located device
{ rack_unit_start => 20, hardware_product_id => $hw_product_storage->id },
# new layout
{ rack_unit_start => 26, hardware_product_id => $hw_product_compute->id },
])
->status_is(303)
->location_is('/rack/'.$rack_id.'/layouts')
->log_debug_is('deleted 2 rack layouts, created 1 rack layouts for rack '.$rack_id);

$t->get_ok('/rack/'.$rack_id.'/layouts')
->status_is(200)
->json_schema_is('RackLayouts')
->json_cmp_deeply([
superhashof({ rack_unit_start => 1, hardware_product_id => $hw_product_compute->id }),
superhashof({ rack_unit_start => 20, hardware_product_id => $hw_product_storage->id }),
superhashof({ rack_unit_start => 26, hardware_product_id => $hw_product_compute->id }),
]);

$t->get_ok('/rack/'.$rack_id.'/assignment')
->status_is(200)
->json_schema_is('RackAssignments')
->json_cmp_deeply([
{ rack_unit_start => 1, rack_unit_size => 2, hardware_product_name => $hw_product_compute->name, device_id => undef, device_asset_tag => undef },
{ rack_unit_start => 20, rack_unit_size => 4, hardware_product_name => $hw_product_storage->name, device_id => $device->id, device_asset_tag => undef },
{ rack_unit_start => 26, rack_unit_size => 2, hardware_product_name => $hw_product_compute->name, device_id => undef, device_asset_tag => undef },
]);

$t->post_ok('/rack/'.$rack_id.'/layouts',
json => [
{ rack_unit_start => 3, hardware_product_id => $hw_product_compute->id },
])
->status_is(303)
->location_is('/rack/'.$rack_id.'/layouts')
->log_debug_is('unlocated 1 devices, deleted 3 rack layouts, created 1 rack layouts for rack '.$rack_id);

$t->get_ok('/rack/'.$rack_id.'/layouts')
->status_is(200)
->json_schema_is('RackLayouts')
->json_cmp_deeply([
superhashof({ rack_unit_start => 3, hardware_product_id => $hw_product_compute->id }),
]);

$t->get_ok('/rack/'.$rack_id.'/assignment')
->status_is(200)
->json_schema_is('RackAssignments')
->json_cmp_deeply([
{ rack_unit_start => 3, rack_unit_size => 2, hardware_product_name => $hw_product_compute->name, device_id => undef, device_asset_tag => undef },
]);

$t->post_ok('/rack/'.$rack_id.'/layouts', json => [])
->status_is(303)
->location_is('/rack/'.$rack_id.'/layouts')
->log_debug_is('deleted 1 rack layouts for rack '.$rack_id);

$t->get_ok('/rack/'.$rack_id.'/layouts')
->status_is(200)
->json_schema_is('RackLayouts')
->json_is([]);

$t->get_ok('/rack/'.$rack_id.'/assignment')
->status_is(200)
->json_schema_is('RackAssignments')
->json_is([]);

done_testing;
# vim: set ts=4 sts=4 sw=4 et :

0 comments on commit 044a3a6

Please sign in to comment.