Skip to content

Commit

Permalink
Feature book host devices (#2057)
Browse files Browse the repository at this point in the history
* test: check access by group
* test: list bases access
* feat(frontend): list local and ldap groups
* feat: list local groups for access settings
* feat: booking local group
* feat: upload members
  • Loading branch information
frankiejol authored May 27, 2024
1 parent 22de9a0 commit d2a948c
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 18 deletions.
15 changes: 12 additions & 3 deletions lib/Ravada.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2347,6 +2347,7 @@ sub _sql_create_tables($self) {
,date_booking => 'date'
,visibility => "enum ('private','public') default 'public'"
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
,options => 'varchar(100)'
}
]
,
Expand Down Expand Up @@ -3717,9 +3718,16 @@ sub list_domains_data($self, %args ) {
$where = " WHERE $where " if $where;
my $query = "SELECT * FROM domains $where ORDER BY name";
my $sth = $CONNECTOR->dbh->prepare($query);

my $sth_hd = $CONNECTOR->dbh->prepare(
"SELECT count(*) FROM host_devices_domain_locked "
." WHERE id_domain=?"
);
$sth->execute(@values);
while (my $row = $sth->fetchrow_hashref) {
$row->{date_changed} = 0 if !defined $row->{date_changed};
$sth_hd->execute($row->{id});
($row->{host_devices})=$sth_hd->fetchrow;
lock_hash(%$row);
push @domains,($row);
}
Expand Down Expand Up @@ -6644,14 +6652,15 @@ sub _shutdown_bookings($self) {
my @bookings = Ravada::Booking::bookings();
return if !scalar(@bookings);


my @domains = $self->list_domains_data(status => 'active');
for my $dom ( @domains ) {
next if $dom->{autostart};
next if $self->_user_is_admin($dom->{id_owner});

if ( Ravada::Booking::user_allowed($dom->{id_owner}, $dom->{id_base}) ) {
# warn "\tuser $dom->{id_owner} allowed to start clones from $dom->{id_base}";
if ( Ravada::Booking::user_allowed($dom->{id_owner}, $dom->{id_base}, $dom->{host_devices})
&& Ravada::Booking::user_allowed($dom->{id_owner}, $dom->{id}, $dom->{host_devices})
) {
#warn "\tuser $dom->{id_owner} allowed to start clones from $dom->{id_base}";
next;
}

Expand Down
8 changes: 5 additions & 3 deletions lib/Ravada/Booking.pm
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ sub BUILD($self, $args) {
my $day_of_week = delete $args->{day_of_week};

my %entry;
my @fields_entry = qw ( bases ldap_groups local_groups users time_start time_end );
my @fields_entry = qw ( bases ldap_groups local_groups users time_start time_end options);
for (@fields_entry) {
$entry{$_} = delete $args->{$_};
}

my %fields = map { $_ => 1 } keys %$args;
delete @fields{'title','id_owner','description','date_created','local_groups'};
delete @fields{'title','id_owner','description','date_created','local_groups','options'};
die "Error: unknown arguments ".(join("," , keys %fields)) if keys %fields;

$self->_insert_db(%$args
Expand Down Expand Up @@ -325,7 +325,7 @@ sub _search_user_name($id_user) {
return $name;
}

sub user_allowed($user,$id_base) {
sub user_allowed($user,$id_base, $enable_host_devices=1) {
my $user_name = $user;
if ( ref($user) ) {
$user_name = $user->name;
Expand All @@ -347,7 +347,9 @@ sub user_allowed($user,$id_base) {
$allowed = 0;
next unless !scalar($entry->bases_id) || grep { $_ == $id_base } $entry->bases_id;
# look no further if user is allowed

return 1 if $entry->user_allowed($user_name);
return 1 if $entry->options_allowed($id_base, $enable_host_devices);
}
if (!$allowed && !ref($user)) {
my $user0 = Ravada::Auth::SQL->new(name => $user_name);
Expand Down
29 changes: 29 additions & 0 deletions lib/Ravada/Booking/Entry.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use strict;

use Carp qw(carp croak);
use Data::Dumper;
use Mojo::JSON qw( encode_json decode_json );

use Ravada::Utils;

use Moose;
Expand Down Expand Up @@ -54,6 +56,9 @@ sub _dbh($self) {

sub _insert_db($self, $field) {

my $options = $field->{options};
$field->{options} = encode_json($options) if $options;

my $query = "INSERT INTO booking_entries "
."(" . join(",",sort keys %$field )." )"
." VALUES (". join(",", map { '?' } keys %$field )." ) "
Expand Down Expand Up @@ -249,6 +254,14 @@ sub _open($self, $id) {
$sth->execute($id);
my $row = $sth->fetchrow_hashref;
confess "Error: Booking entry $id not found " if !keys %$row;
eval {
$row->{options} = decode_json($row->{options}) if $row->{options};
};
if ($@) {
warn "Error decoding options $row->{options} ".$@;
$row->{options} = undef;
}

$self->{_data} = $row;

return $self;
Expand Down Expand Up @@ -277,6 +290,8 @@ sub change($self, %fields) {
} elsif ($field eq 'bases') {
$self->_change_bases($fields{$field});
next;
} elsif ( ref($fields{$field})) {
$fields{$field} = encode_json($fields{$field});
}
next if !exists $self->{_data}->{$field};
my $old_value = $self->_data($field);
Expand Down Expand Up @@ -438,6 +453,20 @@ sub user_allowed($entry, $user_name) {
return 0;
}

sub options_allowed($entry, $id_domain, $enable_host_devices=1) {
my $options = $entry->_data('options');
return 0 if !$options || !ref($options) || !keys %$options;

my $domain = Ravada::Front::Domain->open($id_domain);

if ($options->{host_devices}) {
return 0 if $enable_host_devices && $domain->list_host_devices();
return 1;
}

return 0;
}

sub _remove_users($self) {
my $sth =$self->_dbh->prepare("DELETE FROM booking_entry_users WHERE id_booking_entry=? ");
$sth->execute($self->id);
Expand Down
21 changes: 15 additions & 6 deletions lib/Ravada/Domain.pm
Original file line number Diff line number Diff line change
Expand Up @@ -428,10 +428,19 @@ sub _start_preconditions{
($user) = $_[1];
}
$self->_allowed_start($user);
if ( Ravada->setting('/backend/bookings') && !$self->allowed_booking( $user ) ) {
my @bookings = Ravada::Booking::bookings(date => DateTime->now()->ymd
,time => DateTime->now()->hms);
confess "Error: resource booked ".Dumper(\@bookings);

my $enable_host_devices;
$enable_host_devices = $request->defined_arg('enable_host_devices') if $request;
$enable_host_devices = 1 if !defined $enable_host_devices;

if ( Ravada->setting('/backend/bookings')
&& !$self->allowed_booking( $user, $enable_host_devices ) ) {
my $tz = Ravada::Booking::TZ();
my @bookings = Ravada::Booking::bookings(
date => DateTime->now(time_zone => $tz)->ymd
,time => DateTime->now(time_zone => $tz)->hms);

confess "Error: resource booked for ".join(" , ",(map { $_->_data('title') } @bookings));
}
#_check_used_memory(@_);
$self->status('starting');
Expand All @@ -447,12 +456,12 @@ or its base. Returns false otherwise.
=cut


sub allowed_booking($self, $user) {
sub allowed_booking($self, $user, $enable_hd=1) {
my $id_base = $self->id;
if (!$self->is_base) {
$id_base = $self->_data('id_base') or return 1;
}
return Ravada::Booking::user_allowed($user, $id_base);
return Ravada::Booking::user_allowed($user, $id_base, $enable_hd);
}

sub _start_checks($self, @args) {
Expand Down
4 changes: 3 additions & 1 deletion lib/Ravada/Request.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,9 @@ sub done_recently($self, $seconds=60,$command=undef, $args=undef) {

return Ravada::Request->open($id) if !keys %$args_d;

my $args_found_d = decode_json($args_found);
my $args_found_d = {};
eval { $args_found_d = decode_json($args_found)};
warn "Warning: request $id $@" if $@;
delete $args_found_d->{uid};
delete $args_found_d->{at};

Expand Down
152 changes: 150 additions & 2 deletions t/vm/r30_reserve.t
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use Hash::Util qw(lock_hash);
use Test::More;
use YAML qw(DumpFile);

use Ravada::HostDevice::Templates;

use 5.010;

no warnings "experimental::signatures";
Expand Down Expand Up @@ -577,7 +579,7 @@ sub test_conflict_generic($vm, $base, $conflict_start, $conflict_end, $n_expecte
}


sub _create_booking( $base ) {
sub _create_booking( $base , $options=undef ) {
_wait_end_of_hour();
my $date_start = _yesterday();
my $date_end = _now_days(15);
Expand All @@ -586,8 +588,12 @@ sub _create_booking( $base ) {

my $today = DateTime->from_epoch( epoch => time(), time_zone => $TZ);
my $tomorrow = DateTime->from_epoch( epoch => time(), time_zone => $TZ)->add(days => 1);
my @args;
push @args,(bases => $base->id) if $base;
push @args,(options => $options) if $options;

my $booking = Ravada::Booking->new(
bases => $base->id
@args
, ldap_groups => $GROUP
, users => $USER_YES_NAME_1
, date_start => $date_start
Expand Down Expand Up @@ -1299,6 +1305,144 @@ sub test_config {
is(rvd_back->setting('/backend/bookings'),1);
}

sub _test_list_yes($user, @bases) {
my $list = rvd_front->list_machines_user($user);
for my $base ( @bases ) {
my ($found) = grep { $_->{name} eq $base->name} @$list;
ok($found,"Expecting ".$base->name." in ".Dumper($list));
}
}

sub _test_list_no($user, @bases) {
my $list = rvd_front->list_machines_user($user);
for my $base ( @bases ) {
my ($found) = grep { $_->{name} eq $base->name} @$list;
ok(!$found,"Expecting no ".$base->name." in ".Dumper($list));
}
}


sub _create_base_hd($vm, $id_hd) {
my $base_hd = create_domain($vm);
Ravada::Request->add_hardware(
uid => user_admin->id
,id_domain => $base_hd->id
,name => 'usb'
);
wait_request(debug => 0);
$base_hd->add_host_device($id_hd);
$base_hd->prepare_base(user_admin);
$base_hd->is_public(1);
return $base_hd;
}

sub test_booking_host_devices($vm) {
my $templates = Ravada::HostDevice::Templates::list_templates($vm->id);
my ($usb_hd) = grep { $_->{name} =~ /USB/ } @$templates;

die "Error: no USB template found ".Dumper($templates) if !$usb_hd;

my $id_hd = $vm->add_host_device(template => $usb_hd->{name});
my $hd = Ravada::HostDevice->search_by_id($id_hd);

if ($vm->type eq 'KVM') {
my $config = config_host_devices('usb');
if (!$config) {
diag("No USB config in t/etc/host_devices.conf");
return;
}
$hd->_data('list_filter' => $config);
}

my $base = create_domain($vm);
$base->prepare_base(user_admin);
$base->is_public(1);

my $base_hd = _create_base_hd($vm, $id_hd);

_test_list_yes($USER_LOCAL_YES_1, $base, $base_hd);
_test_list_yes($USER_LOCAL_NO, $base, $base_hd);

my $booking = _create_booking(undef , { host_devices => 1 } );

for my $entry ( $booking->entries ) {
$entry->change('time_end' => _now_seconds(120));
ok($entry->_data('options')) or exit;
is($entry->_data('options')->{host_devices},1) or die Dumper($entry->_data('options'));
$entry->change( local_groups => $GROUP_LOCAL->id );
}
my ($entry) = $booking->entries;
$entry->change('options' => { host_devices => 2 });
my ($entry_changed) = $booking->entries;
is($entry_changed->_data('options')->{host_devices},2) or die Dumper($entry_changed->_data('options'));
$entry->change('options' => { host_devices => 1 });

_test_list_yes($USER_LOCAL_YES_1, $base, $base_hd);
_test_list_yes($USER_LOCAL_NO, $base);
_test_list_no($USER_LOCAL_NO, $base_hd);
#####
#
# list yes

#####
#
# list no
my $list_no = rvd_front->list_machines_user($USER_LOCAL_NO);
ok(grep { $_->{name} eq $base->name} @$list_no) or die Dumper($list_no);
ok(!grep { $_->{name} eq $base_hd->name} @$list_no) or die Dumper($list_no);

my $clone_yes = $base->clone(name => new_domain_name, user => $USER_LOCAL_YES_1);
my $clone_hd_yes = $base_hd->clone(name => new_domain_name, user => $USER_LOCAL_YES_1);

# user allowed can start anything
is(Ravada::Booking::user_allowed($USER_LOCAL_YES_1, $clone_yes->id),1);
is(Ravada::Booking::user_allowed($USER_LOCAL_YES_1, $clone_hd_yes->id),1);
for my $c ( $clone_yes, $clone_hd_yes) {
my $req_start_clone = Ravada::Request->start_domain(
uid => $c->id_owner
,id_domain => $c->id
);
wait_request(check_error => 0);
is($req_start_clone->error,'');
is ($c->is_active,1);
}

my $clone_no = $base->clone(name => new_domain_name, user => $USER_LOCAL_NO);
my $clone_hd_no = $base_hd->clone(name => new_domain_name, user => $USER_LOCAL_NO);

# User allowed only to non_hd bases
# allowed
is(Ravada::Booking::user_allowed($USER_LOCAL_NO, $clone_yes->id),1) or exit;
is(Ravada::Booking::user_allowed($USER_LOCAL_NO, $base->id),1) or exit;
# denied
is(Ravada::Booking::user_allowed($USER_LOCAL_NO, $clone_hd_yes->id),0);
is(Ravada::Booking::user_allowed($USER_LOCAL_NO, $base_hd->id),0);
# allow when disabled host_devices
is(Ravada::Booking::user_allowed($USER_LOCAL_NO, $clone_hd_yes->id,0),1);

# can start when forcing no host devices
my $req_start_hd_without = Ravada::Request->start_domain(uid => $clone_hd_no->id_owner, id_domain => $clone_hd_no->id, enable_host_devices => 0);

Ravada::Request->enforce_limits(_force => 1);
wait_request(check_error => 0, debug => 0);
is($req_start_hd_without->error,'');
is($clone_hd_no->is_active,1) or die $clone_hd_no->name;

# user denied can not start hd
my $req_start_no = Ravada::Request->start_domain(uid => $clone_no->id_owner, id_domain => $clone_no->id);
my $req_start_hd_no = Ravada::Request->start_domain(uid => $clone_hd_no->id_owner, id_domain => $clone_hd_no->id);
wait_request(check_error => 0);
is($req_start_no->error,'');
like($req_start_hd_no->error,qr/./);

is($clone_no->is_active,1) or die $clone_no->name;
is($clone_hd_no->is_active,0);

$booking->remove();
remove_domain($base);
remove_domain($base_hd);
}

###################################################################

test_config();
Expand All @@ -1324,6 +1468,10 @@ for my $vm_name ( vm_names()) {

skip($msg,10) if !$vm;

diag("Testing booking in $vm_name");

test_booking_host_devices($vm);

test_bookings_week_2days($vm);
test_search_change_remove_booking($vm);

Expand Down
Loading

0 comments on commit d2a948c

Please sign in to comment.