diff --git a/lib/Ravada.pm b/lib/Ravada.pm index 35074405f..d19316a71 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -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)' } ] , @@ -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); } @@ -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; } diff --git a/lib/Ravada/Booking.pm b/lib/Ravada/Booking.pm index bb6ea22c2..1a49e6472 100644 --- a/lib/Ravada/Booking.pm +++ b/lib/Ravada/Booking.pm @@ -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 @@ -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; @@ -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); diff --git a/lib/Ravada/Booking/Entry.pm b/lib/Ravada/Booking/Entry.pm index 3e3bc1a1c..b4bc3497a 100644 --- a/lib/Ravada/Booking/Entry.pm +++ b/lib/Ravada/Booking/Entry.pm @@ -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; @@ -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 )." ) " @@ -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; @@ -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); @@ -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); diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index 57266fb8b..5e85598e0 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -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'); @@ -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) { diff --git a/lib/Ravada/Request.pm b/lib/Ravada/Request.pm index 67473ed26..03bea87d3 100644 --- a/lib/Ravada/Request.pm +++ b/lib/Ravada/Request.pm @@ -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}; diff --git a/t/vm/r30_reserve.t b/t/vm/r30_reserve.t index aeaf4c28f..e14dc7479 100644 --- a/t/vm/r30_reserve.t +++ b/t/vm/r30_reserve.t @@ -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"; @@ -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); @@ -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 @@ -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(); @@ -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); diff --git a/templates/booking/formEvent.component.html.ep b/templates/booking/formEvent.component.html.ep index a7a951b1d..51b1e16c0 100644 --- a/templates/booking/formEvent.component.html.ep +++ b/templates/booking/formEvent.component.html.ep @@ -108,6 +108,20 @@ +
+
+ <%=l 'Options' %> +
+
+
+ + +
+
+