Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package openQA for openSUSE:Factory checked in at 2025-12-01 11:12:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.14147 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Mon Dec 1 11:12:56 2025 rev:777 rq:1320530 version:5.1764349525.ffb59486 Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2025-11-24 14:11:27.707002883 +0100 +++ /work/SRC/openSUSE:Factory/.openQA.new.14147/openQA.changes 2025-12-01 11:13:00.485711717 +0100 @@ -1,0 +2,25 @@ +Fri Nov 28 20:38:53 UTC 2025 - [email protected] + +- Update to version 5.1764349525.ffb59486: + * Also use TIMEOUT_SCALE for priority malus calculation + * docs: Fix wrapping and typo + * Document multi machine ovs flow setup and IPv6 usage + * Avoid computing time constraint for scheduled product cleanup in Perl + * rpm: Move `…-enqueue-needle-ref-cleanup` to other `…-enqueue-…` scripts + * Add task to limit scheduled products similar to audit events + * Extract generic parts from audit event cleanup task into generic task + * parser: ktap: Show full output by default if no line was parsed + * Ignore npm scripts also via `.npmrc` to make bare npm calls more secure + * Avoid repeating `MAIN_SETTINGS` in various places + * Fix possibly excessive memory use when computer test result overview + * Fix typo in `_prepare_complex_query_search_args` + * Fix indentation in `overview.html.ep` + * Prevent logging AMQP credentials in debug output + * Make restart_openqa_job emit proper event payload + * Enable gru tasks to emit AMQP messages + * Remove explicit loading AMQP plugin in Gru plugin + * Emit restart events when job restarted automatically + * Add debug message about priority malus + * Fix ordering of job groups after 2ad929ceca43d + +------------------------------------------------------------------- Old: ---- openQA-5.1763743683.1da97aa2.obscpio New: ---- openQA-5.1764349525.ffb59486.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.95quJ0/_old 2025-12-01 11:13:04.797894122 +0100 +++ /var/tmp/diff_new_pack.95quJ0/_new 2025-12-01 11:13:04.825895306 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1763743683.1da97aa2 +Version: 5.1764349525.ffb59486 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-devel-test.spec ++++++ --- /var/tmp/diff_new_pack.95quJ0/_old 2025-12-01 11:13:05.097906812 +0100 +++ /var/tmp/diff_new_pack.95quJ0/_new 2025-12-01 11:13:05.105907150 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-devel Name: %{short_name}-test -Version: 5.1763743683.1da97aa2 +Version: 5.1764349525.ffb59486 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.95quJ0/_old 2025-12-01 11:13:05.229912396 +0100 +++ /var/tmp/diff_new_pack.95quJ0/_new 2025-12-01 11:13:05.229912396 +0100 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1763743683.1da97aa2 +Version: 5.1764349525.ffb59486 Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.95quJ0/_old 2025-12-01 11:13:05.349917472 +0100 +++ /var/tmp/diff_new_pack.95quJ0/_new 2025-12-01 11:13:05.349917472 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1763743683.1da97aa2 +Version: 5.1764349525.ffb59486 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.95quJ0/_old 2025-12-01 11:13:05.481923056 +0100 +++ /var/tmp/diff_new_pack.95quJ0/_new 2025-12-01 11:13:05.485923225 +0100 @@ -17,7 +17,7 @@ %define openqa_main_service openqa-webui.service -%define openqa_extra_services openqa-gru.service openqa-websockets.service openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service openqa-enqueue-needle-ref-cleanup.timer +%define openqa_extra_services openqa-gru.service openqa-websockets.service openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service openqa-enqueue-needle-ref-cleanup.timer openqa-enqueue-scheduled-product-cleanup.service openqa-enqueue-scheduled-product-cleanup.timer %define openqa_services %{openqa_main_service} %{openqa_extra_services} %define openqa_worker_services openqa-worker.target openqa-slirpvde.service openqa-vde_switch.service openqa-worker-cacheservice.service openqa-worker-cacheservice-minion.service %define openqa_localdb_services openqa-setup-db.service openqa-dump-db.service openqa-dump-db.timer @@ -99,7 +99,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1763743683.1da97aa2 +Version: 5.1764349525.ffb59486 Release: 0 Summary: The openQA web-frontend, scheduler and tools License: GPL-2.0-or-later @@ -633,6 +633,8 @@ %{_unitdir}/openqa-enqueue-bug-cleanup.timer %{_unitdir}/openqa-enqueue-needle-ref-cleanup.service %{_unitdir}/openqa-enqueue-needle-ref-cleanup.timer +%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.service +%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.timer %{_tmpfilesdir}/openqa-webui.conf # web libs %dir %{_datadir}/openqa @@ -664,13 +666,14 @@ %{_datadir}/openqa/script/openqa-enqueue-audit-event-cleanup %{_datadir}/openqa/script/openqa-enqueue-bug-cleanup %{_datadir}/openqa/script/openqa-enqueue-git-auto-update +%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup %{_datadir}/openqa/script/openqa-enqueue-result-cleanup +%{_datadir}/openqa/script/openqa-enqueue-scheduled-product-cleanup %{_datadir}/openqa/script/openqa-gru %{_datadir}/openqa/script/openqa-rollback %{_datadir}/openqa/script/openqa-webui-daemon %{_datadir}/openqa/script/upgradedb %{_datadir}/openqa/script/modify_needle -%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup # TODO: define final user %defattr(-,geekotest,root) # attention: never package subdirectories owned by a user other ++++++ openQA-5.1763743683.1da97aa2.obscpio -> openQA-5.1764349525.ffb59486.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/.npmrc new/openQA-5.1764349525.ffb59486/.npmrc --- old/openQA-5.1763743683.1da97aa2/.npmrc 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/.npmrc 2025-11-28 18:05:25.000000000 +0100 @@ -0,0 +1,2 @@ +# for the sake of security as we don't need scripts anyway +ignore-scripts=true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/dist/rpm/openQA.spec new/openQA-5.1764349525.ffb59486/dist/rpm/openQA.spec --- old/openQA-5.1763743683.1da97aa2/dist/rpm/openQA.spec 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/dist/rpm/openQA.spec 2025-11-28 18:05:25.000000000 +0100 @@ -17,7 +17,7 @@ # can't use linebreaks here! %define openqa_main_service openqa-webui.service -%define openqa_extra_services openqa-gru.service openqa-websockets.service openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service openqa-enqueue-needle-ref-cleanup.timer +%define openqa_extra_services openqa-gru.service openqa-websockets.service openqa-scheduler.service openqa-enqueue-audit-event-cleanup.service openqa-enqueue-audit-event-cleanup.timer openqa-enqueue-asset-cleanup.service openqa-enqueue-git-auto-update.service openqa-enqueue-asset-cleanup.timer openqa-enqueue-result-cleanup.service openqa-enqueue-result-cleanup.timer openqa-enqueue-bug-cleanup.service openqa-enqueue-bug-cleanup.timer openqa-enqueue-git-auto-update.timer openqa-enqueue-needle-ref-cleanup.service openqa-enqueue-needle-ref-cleanup.timer openqa-enqueue-scheduled-product-cleanup.service openqa-enqueue-scheduled-product-cleanup.timer %define openqa_services %{openqa_main_service} %{openqa_extra_services} %define openqa_worker_services openqa-worker.target openqa-slirpvde.service openqa-vde_switch.service openqa-worker-cacheservice.service openqa-worker-cacheservice-minion.service %define openqa_localdb_services openqa-setup-db.service openqa-dump-db.service openqa-dump-db.timer @@ -633,6 +633,8 @@ %{_unitdir}/openqa-enqueue-bug-cleanup.timer %{_unitdir}/openqa-enqueue-needle-ref-cleanup.service %{_unitdir}/openqa-enqueue-needle-ref-cleanup.timer +%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.service +%{_unitdir}/openqa-enqueue-scheduled-product-cleanup.timer %{_tmpfilesdir}/openqa-webui.conf # web libs %dir %{_datadir}/openqa @@ -664,13 +666,14 @@ %{_datadir}/openqa/script/openqa-enqueue-audit-event-cleanup %{_datadir}/openqa/script/openqa-enqueue-bug-cleanup %{_datadir}/openqa/script/openqa-enqueue-git-auto-update +%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup %{_datadir}/openqa/script/openqa-enqueue-result-cleanup +%{_datadir}/openqa/script/openqa-enqueue-scheduled-product-cleanup %{_datadir}/openqa/script/openqa-gru %{_datadir}/openqa/script/openqa-rollback %{_datadir}/openqa/script/openqa-webui-daemon %{_datadir}/openqa/script/upgradedb %{_datadir}/openqa/script/modify_needle -%{_datadir}/openqa/script/openqa-enqueue-needle-ref-cleanup # TODO: define final user %defattr(-,geekotest,root) # attention: never package subdirectories owned by a user other diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/docs/Networking.asciidoc new/openQA-5.1764349525.ffb59486/docs/Networking.asciidoc --- old/openQA-5.1763743683.1da97aa2/docs/Networking.asciidoc 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/docs/Networking.asciidoc 2025-11-28 18:05:25.000000000 +0100 @@ -100,6 +100,15 @@ The name of the bridge (default: `br1`) will be set in `/etc/sysconfig/os-autoinst-openvswitch`. +A huge part of the magic happens in os-autoinst-openvswitch. On startup this +daemon will create flow rules in the OVS bridge that allow the SUT VMs to talk +to upstream networks despite being in a VLAN to isolate from other tests. +These flow rules implement an address translation from the SUT IPs to +intermediate IPs so that it doesn't matter if multiple tests are running at +the same time with conflicting IP addresses. These intermediate IP addresses +can then be NATed (again) to upstream networks by regular iptables/NFT +masquerading rules. + ===== Configure virtual interfaces The script will add the bridge device and the tap devices for every @@ -112,6 +121,12 @@ for further information. ===== Configure NAT with firewalld + +NOTE: `os-autoinst-setup-multi-machine` can set this up for you, but using +plain NFT instead (which `os-autoinst-setup-multi-machine` can setup as well) +or firewalld is recommended due to firewalld's suboptimal performance with +many tap interfaces present. + The required firewall rules for masquerading (NAT) and zone configuration for the trusted zone will be set up. The bridge devices will be added to the zone. @@ -233,9 +248,16 @@ ``` ip link set dev eth0 up mtu 1380 + +# for ipv4 ip a add dev eth0 10.0.2.15/24 ip r add default via 10.0.2.2 echo 'nameserver 8.8.8.8' > /etc/resolv.conf + +# for ipv6 +ip a add dev eth0 fec0::15/64 +ip r add default via fec0::2 +echo 'nameserver 2606:4700:4700::1111' > /etc/resolv.conf ``` The MTU is chosen in accordance with what the openSUSE test distribution uses diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/docs/UsersGuide.asciidoc new/openQA-5.1764349525.ffb59486/docs/UsersGuide.asciidoc --- old/openQA-5.1763743683.1da97aa2/docs/UsersGuide.asciidoc 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/docs/UsersGuide.asciidoc 2025-11-28 18:05:25.000000000 +0100 @@ -1599,6 +1599,7 @@ - limit_audit_events - limit_bugs - limit_results_and_logs +- limit_scheduled_products - limit_screenshots These are no-ops if a task is already running so they can safely be enqueued diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/etc/openqa/openqa.ini new/openQA-5.1764349525.ffb59486/etc/openqa/openqa.ini --- old/openQA-5.1763743683.1da97aa2/etc/openqa/openqa.ini 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/etc/openqa/openqa.ini 2025-11-28 18:05:25.000000000 +0100 @@ -333,6 +333,11 @@ ## job priority. Can be 0 to disable and negative to prioritize long running job ## scenarios. #max_job_time_prio_scale = 100 +## Minimum storage duration for scheduled products +## Scheduled products are kept as long as the jobs they have created exist. So their retention is controlled by the +## retention of their jobs and you normally don't need to tweak this value. It only serves as a minimum retention +## so scheduled products without any jobs (e.g. due to errors) are not immediately cleaned up. +#scheduled_product_min_storage_duration = 34 [archiving] ## Moves logs of jobs which are preserved during the cleanup because they are considered important diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Parser/Format/KTAP.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Parser/Format/KTAP.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Parser/Format/KTAP.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Parser/Format/KTAP.pm 2025-11-28 18:05:25.000000000 +0100 @@ -48,7 +48,11 @@ $self->test or return; my $line = $result->as_string; - return unless $line =~ /^#\s*(?<status>ok|not ok)\s+(?<index>\d+)\s+(?<name>[^#]*)/; + unless ($line =~ /^#\s*(?<status>ok|not ok)\s+(?<index>\d+)\s+(?<name>[^#]*)/) { + push @{$steps->{unparsed_lines}}, $line; + return; + } + $steps->{parsed_lines_count}++; return if $line =~ /#\s*SKIP\b/i; my ($status, $index, $subtest_name) = @+{qw(status index name)}; @@ -92,6 +96,22 @@ $steps->{name} = $self->test->{name}; $steps->{test} = $self->test; + + # If there are no parsed lines, default to simply show the whole log. + # This is better than not showing anything, impeding the user to see + # the buttons of adding product or test bugs and requiring them to + # search and open individual log assets. + if (!$steps->{parsed_lines_count}) { + my $filename = "KTAP-@{[$steps->{name}]}.txt"; + push @{$steps->{details}}, + { + text => $filename, + title => $steps->{name}, + result => 'ok', + }; + $self->_add_output({file => $filename, content => join("\n", @{$steps->{unparsed_lines}})}); + } + $self->generated_tests_results->add(OpenQA::Parser::Result::OpenQA->new($steps)); $self->test(undef); @@ -106,11 +126,11 @@ while (my $result = $parser->next) { next if $result->type eq 'version' || $result->type eq 'plan'; - if ($result->type eq 'comment' && $result->as_string =~ /^#\s*selftests:\s+/) { - $self->_testgroup_init($result->as_string); - next; - } - if ($result->type eq 'comment' && $result->as_string =~ /^#\s*(ok|not ok)\s+\d+\s+/) { + if ($result->type eq 'comment') { + if ($result->as_string =~ /^#\s*selftests:\s+/) { + $self->_testgroup_init($result->as_string); + next; + } $self->_parse_subtest($result); next; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/Result/Jobs.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/Result/Jobs.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/Result/Jobs.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/Result/Jobs.pm 2025-11-28 18:05:25.000000000 +0100 @@ -418,7 +418,7 @@ $settings->{$_->key} = $_->value; } } - for my $column (qw(DISTRI VERSION FLAVOR MACHINE ARCH BUILD TEST)) { + for my $column (MAIN_SETTINGS) { if (my $value = $self->$column) { $settings->{$column} = $value } } $settings->{NAME} = sprintf '%08d-%s', $self->id, $self->name; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/AuditEvents.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/AuditEvents.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 2025-11-28 18:05:25.000000000 +0100 @@ -24,7 +24,7 @@ needle => 'needle_%', ); -sub delete_entries_exceeding_storage_duration { +sub delete_expired_entries { my ($self, %options) = @_; my @event_type_globs; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/JobGroupParents.pm 2025-11-28 18:05:25.000000000 +0100 @@ -10,7 +10,8 @@ sub job_groups_and_parents { my $self = shift; - my $params = {order_by => [{-asc => 'me.sort_order'}, {-asc => 'me.name'}], prefetch => 'children'}; + my @order = qw(me.sort_order children.sort_order me.name children.name); + my $params = {order_by => \@order, prefetch => 'children'}; my @parents = $self->search({}, $params)->all; my $schema = $self->result_source->schema; my @groups_without_parent = $schema->resultset('JobGroups') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/Jobs.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/Jobs.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/Jobs.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/Jobs.pm 2025-11-28 18:05:25.000000000 +0100 @@ -23,6 +23,7 @@ use Mojolicious::Validator::Validation; use Time::HiRes 'time'; use DateTime; +use List::Util qw(any); use Scalar::Util qw(looks_like_number); =head2 latest_build @@ -55,20 +56,16 @@ $attrs{order_by} = {-desc => 'me.id'}; # More reliable for tests than t_created $attrs{columns} = qw(BUILD); + my $job_settings = $schema->resultset('JobSettings'); foreach my $key (keys %args) { + my $key_uc = uc $key; my $value = $args{$key}; - - if (grep { $key eq $_ } qw(distri version flavor machine arch build test)) { - push(@conds, {'me.' . uc($key) => $value}); + if (any { $key_uc eq $_ } OpenQA::Schema::Result::Jobs::MAIN_SETTINGS) { + push @conds, {'me.' . $key_uc => $value}; } else { - - my $subquery = $schema->resultset('JobSettings')->search( - { - key => uc($key), - value => $value - }); - push(@conds, {'me.id' => {-in => $subquery->get_column('job_id')->as_query}}); + my $subquery = $job_settings->search({key => $key_uc, value => $value}); + push @conds, {'me.id' => {-in => $subquery->get_column('job_id')->as_query}}; } } @@ -97,16 +94,8 @@ my @latest; my %seen; foreach my $job (@jobs) { - my $test = $job->TEST; - my $distri = $job->DISTRI; - my $version = $job->VERSION; - my $build = $job->BUILD; - my $flavor = $job->FLAVOR; - my $arch = $job->ARCH; - my $machine = $job->MACHINE // ''; - my $key = "$distri-$version-$build-$test-$flavor-$arch-$machine"; - next if $seen{$key}++; - push(@latest, $job); + my $key = join('-', map { $job->$_ // '' } OpenQA::Schema::Result::Jobs::MAIN_SETTINGS); + push @latest, $job unless $seen{$key}++; } return @latest; @@ -157,12 +146,21 @@ # assign scheduled product $new_job_args{scheduled_product_id} = $scheduled_product_id; - if (looks_like_number($settings{MAX_JOB_TIME}) and $settings{MAX_JOB_TIME} > DEFAULT_MAX_JOB_TIME) { - my $scale = OpenQA::App->singleton->config->{misc_limits}->{max_job_time_prio_scale}; - $new_job_args{priority} += int($settings{MAX_JOB_TIME} / $scale) if $scale; + my $debug_msg; + my $max_job_time = looks_like_number $settings{MAX_JOB_TIME} ? $settings{MAX_JOB_TIME} : 0; + my $timeout_scale = looks_like_number $settings{TIMEOUT_SCALE} ? $settings{TIMEOUT_SCALE} : 0; + $max_job_time *= $timeout_scale if $max_job_time and $timeout_scale > 1; + if ($max_job_time and $max_job_time > DEFAULT_MAX_JOB_TIME) { + if (my $scale = OpenQA::App->singleton->config->{misc_limits}->{max_job_time_prio_scale}) { + my $malus = int($max_job_time / $scale); + $debug_msg = sprintf 'Adding priority malus to newly created job (old: %d, malus: %s)', + $new_job_args{priority}, $malus; + $new_job_args{priority} += $malus; + } } my $job = $self->create(\%new_job_args); + log_debug(sprintf "(Job %d) $debug_msg", $job->id) if $debug_msg; # add job settings my @job_settings; @@ -315,7 +313,7 @@ else { my %js_settings; # Check if the settings are between the arguments passed via query url - # they come in lowercase, so mace sure $key is lc'ed + # they come in lowercase, so make sure $key is lc'ed for my $key (qw(ISO HDD_1 WORKER_CLASS)) { $js_settings{$key} = $args->{lc $key} if defined $args->{lc $key}; } @@ -347,16 +345,53 @@ return (\@conds, \%attrs); } -sub complex_query ($self, %args) { - # For args where we accept a list of values, allow passing either an - # array ref or a comma-separated list +sub _accept_comma_separated_arg_values ($args) { for my $arg (qw(state ids result modules modules_result)) { - next unless $args{$arg}; - $args{$arg} = [split(',', $args{$arg})] unless (ref($args{$arg}) eq 'ARRAY'); + next unless my $value = $args->{$arg}; + $args->{$arg} = [split ',', $value] unless ref $value eq 'ARRAY'; } +} + +sub complex_query ($self, %args) { + _accept_comma_separated_arg_values(\%args); my ($conds, $attrs) = $self->_prepare_complex_query_search_args(\%args); - my $jobs = $self->search({-and => $conds}, $attrs); - return $jobs; + return $self->search({-and => $conds}, $attrs); +} + +sub complex_query_latest_ids ($self, %args) { + # prepare basic search conditions and attributes + _accept_comma_separated_arg_values(\%args); + my ($conds, $attrs) = $self->_prepare_complex_query_search_args(\%args); + my $filters = $args{filters}; + my $has_filters = $filters && @$filters > 0; + my $rows = $has_filters ? delete $attrs->{rows} : undef; # when filtering, limit rows only in outer/filtering query + if (my $until = $args{until}) { push @$conds, {'me.t_created' => {'<=' => $until}} } + + # set attributes to return only the latest job IDs for a certain combination of TEST, DISTRI, VERSION, … + $attrs->{order_by} = \['max(me.id) DESC']; + $attrs->{select} = ['max(me.id)']; + $attrs->{as} = ['id']; + $attrs->{group_by} = [OpenQA::Schema::Result::Jobs::MAIN_SETTINGS]; + + # execute the search; use a sub query if filtering is enabled + my $search = $self->search({-and => $conds}, $attrs); + if ($has_filters) { + # add another layer of querying for filters + # note: The filtering cannot be applied in the same query we do the grouping to return only the latest job IDs. + # Otherwise adding filter parameters would lead to old jobs showing up. That is not wanted (and we have + # therefore the test "filtering does not reveal old jobs" in `10-tests_overview.t` to test this). + my %filter_attrs = %$attrs; + $filter_attrs{rows} = $rows; + push @$filters, {'me.id' => {-in => $search->as_query}}; + $search = $self->search({-and => $filters}, \%filter_attrs); + } + return [map { $_->id } $search->all]; +} + +sub latest_jobs_from_ids ($self, $latest_job_ids, $limit_from_initial_search) { + my %search_args = (id => {-in => $latest_job_ids}); + my %options = (order_by => {-desc => 'id'}, rows => $limit_from_initial_search - 1); + return $self->search(\%search_args, \%options); } sub cancel_by_settings ( @@ -374,7 +409,7 @@ my %precond = %{$settings}; my %cond; - for my $key (qw(DISTRI VERSION FLAVOR MACHINE ARCH BUILD TEST)) { + for my $key (OpenQA::Schema::Result::Jobs::MAIN_SETTINGS) { $cond{$key} = delete $precond{$key} if defined $precond{$key}; } if (keys %precond) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Schema/ResultSet/ScheduledProducts.pm 2025-11-28 18:05:25.000000000 +0100 @@ -118,4 +118,18 @@ return $sth->fetchall_hashref([qw(latest_job_state latest_job_result)]); } +sub delete_expired_entries ($self) { + # delete all scheduled products without jobs that are older than "scheduled_product_min_storage_duration" + my $min_storage_duration = OpenQA::App->singleton->config->{misc_limits}->{scheduled_product_min_storage_duration}; + my $sth = $self->result_source->schema->storage->dbh->prepare( + <<~'END_SQL' + DELETE FROM scheduled_products + WHERE + (t_created < current_date - ?::interval) + AND (SELECT count(id) FROM jobs WHERE jobs.scheduled_product_id = scheduled_products.id LIMIT 1) = 0 + END_SQL + ); + $sth->execute("$min_storage_duration days"); +} + 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Setup.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Setup.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Setup.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Setup.pm 2025-11-28 18:05:25.000000000 +0100 @@ -240,6 +240,7 @@ worker_limit_retry_delay => ONE_HOUR / 4, mcp_max_result_size => 500000, max_job_time_prio_scale => 100, + scheduled_product_min_storage_duration => 34, }, archiving => { archive_preserved_important_jobs => 0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Shared/Plugin/Gru.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Shared/Plugin/Gru.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Shared/Plugin/Gru.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Shared/Plugin/Gru.pm 2025-11-28 18:05:25.000000000 +0100 @@ -44,6 +44,7 @@ OpenQA::Task::Job::Restart OpenQA::Task::Iso::Schedule OpenQA::Task::Bug::Limit + OpenQA::Task::ScheduledProduct::Limit ); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/AuditEvents/Limit.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/AuditEvents/Limit.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/AuditEvents/Limit.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/AuditEvents/Limit.pm 2025-11-28 18:05:25.000000000 +0100 @@ -1,28 +1,10 @@ -# Copyright 2019-2021 SUSE LLC +# Copyright SUSE LLC # SPDX-License-Identifier: GPL-2.0-or-later package OpenQA::Task::AuditEvents::Limit; -use Mojo::Base 'Mojolicious::Plugin'; -use OpenQA::Task::Utils qw(acquire_limit_lock_or_retry); -use OpenQA::Task::SignalGuard; -use Time::Seconds; +use Mojo::Base 'OpenQA::Task::Table::Limit'; -sub register { - my ($self, $app) = @_; - $app->minion->add_task(limit_audit_events => sub { _limit($app, @_) }); -} - -sub _limit { - my ($app, $job) = @_; - my $ensure_task_retry_on_termination_signal_guard = OpenQA::Task::SignalGuard->new($job); - - # prevent multiple limit_audit_events tasks to run in parallel - return $job->finish('Previous limit_audit_events job is still active') - unless my $guard = $app->minion->guard('limit_audit_events_task', ONE_DAY); - - return undef unless my $limit_guard = acquire_limit_lock_or_retry($job); - - $app->schema->resultset('AuditEvents')->delete_entries_exceeding_storage_duration; -} +has task_name => 'limit_audit_events'; +has table => 'AuditEvents'; 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Job/Restart.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Job/Restart.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Job/Restart.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Job/Restart.pm 2025-11-28 18:05:25.000000000 +0100 @@ -5,6 +5,7 @@ use Mojo::Base 'Mojolicious::Plugin', -signatures; use Time::Seconds; +use OpenQA::Events; sub register ($self, $app, @args) { $app->minion->add_task(restart_job => \&_restart_job); @@ -17,6 +18,11 @@ sub restart_openqa_job ($minion_job, $openqa_job) { my $cloned_job_or_error = $openqa_job->auto_duplicate; my $is_ok = ref $cloned_job_or_error || $cloned_job_or_error =~ qr/(already.*clone|direct parent)/i; + if (ref $cloned_job_or_error) { + my %event_data = (id => $openqa_job->id, result => $cloned_job_or_error->{cluster_cloned}, auto => 1); + OpenQA::Events->singleton->emit_event('openqa_job_restart', data => \%event_data); + } + $minion_job->note( ref $cloned_job_or_error ? (cluster_cloned => $cloned_job_or_error->{cluster_cloned}) @@ -33,8 +39,11 @@ my $openqa_job = $app->schema->resultset('Jobs')->find($openqa_job_id); return $minion_job->finish("Job $openqa_job_id does not exist.") unless $openqa_job; + _init_amqp_plugin($app); # duplicate job and finish normally if no error was returned or job can not be cloned my ($is_ok, $cloned_job_or_error) = restart_openqa_job($minion_job, $openqa_job); + _wait_for_event_publish($app); + return $minion_job->finish(ref $cloned_job_or_error ? undef : $cloned_job_or_error) if $is_ok; # retry a certain number of times, maybe the transaction failed due to a conflict @@ -46,4 +55,16 @@ $minion_job->retry({delay => restart_delay}); } +sub _init_amqp_plugin ($app) { + return undef unless $app->config->{amqp}->{enabled}; + $app->plugin('AMQP'); # Needs to be loaded again from forked process + Mojo::IOLoop->singleton->one_tick; +} + +sub _wait_for_event_publish ($app) { + return undef unless $app->config->{amqp}->{enabled}; + OpenQA::Events->singleton->once(amqp_handled => sub { Mojo::IOLoop->stop }); + Mojo::IOLoop->start; +} + 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/ScheduledProduct/Limit.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/ScheduledProduct/Limit.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/ScheduledProduct/Limit.pm 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/ScheduledProduct/Limit.pm 2025-11-28 18:05:25.000000000 +0100 @@ -0,0 +1,10 @@ +# Copyright SUSE LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +package OpenQA::Task::ScheduledProduct::Limit; +use Mojo::Base 'OpenQA::Task::Table::Limit'; + +has task_name => 'limit_scheduled_products'; +has table => 'ScheduledProducts'; + +1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Table/Limit.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Table/Limit.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/Task/Table/Limit.pm 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/Task/Table/Limit.pm 2025-11-28 18:05:25.000000000 +0100 @@ -0,0 +1,29 @@ +# Copyright SUSE LLC +# SPDX-License-Identifier: GPL-2.0-or-later + +package OpenQA::Task::Table::Limit; +use Mojo::Base 'Mojolicious::Plugin', -signatures; +use OpenQA::Task::Utils qw(acquire_limit_lock_or_retry); +use OpenQA::Task::SignalGuard; +use Time::Seconds; + +has 'task_name'; +has 'table'; + +sub register ($self, $app, @) { + $app->minion->add_task($self->task_name => sub { $self->_limit($app, @_) }); +} + +sub _limit ($self, $app, $job, @) { + my $ensure_task_retry_on_termination_signal_guard = OpenQA::Task::SignalGuard->new($job); + + # prevent multiple limit tasks to run in parallel + my $task_name = $self->task_name; + my $guard = $app->minion->guard("${task_name}_task", ONE_DAY); + return $job->finish("Previous $task_name job is still active") unless $guard; + return undef unless my $limit_guard = acquire_limit_lock_or_retry($job); + + $app->schema->resultset($self->table)->delete_expired_entries; +} + +1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 2025-11-28 18:05:25.000000000 +0100 @@ -226,29 +226,15 @@ sub overview ($self) { my ($search_args, $groups) = $self->compose_job_overview_search_args; my $failed_modules = $self->param_hash('failed_modules'); - my $states = $self->param_hash('state'); - my $results = $self->param_hash('result'); - my $archs = $self->param_hash('arch'); - my $machines = $self->param_hash('machine'); - - my @jobs = $self->schema->resultset('Jobs')->complex_query(%$search_args)->latest_jobs; + my $jobs_rs = $self->schema->resultset('Jobs'); + my $latest_job_ids = $jobs_rs->complex_query_latest_ids(%$search_args); + my @jobs = $jobs_rs->latest_jobs_from_ids($latest_job_ids, $search_args->{limit})->all; my @job_hashes; for my $job (@jobs) { - next if $states && !$states->{$job->state}; - next if $results && !$results->{$job->result}; - next if $archs && !$archs->{$job->ARCH}; - next if $machines && !$machines->{$job->MACHINE}; - if ($failed_modules) { - next if $job->result ne OpenQA::Jobs::Constants::FAILED; - next unless OpenQA::Utils::any_array_item_contained_by_hash($job->failed_modules, $failed_modules); - } - push( - @job_hashes, - { - id => $job->id, - name => $job->name, - }); + next + if $failed_modules && !OpenQA::Utils::any_array_item_contained_by_hash($job->failed_modules, $failed_modules); + push @job_hashes, {id => $job->id, name => $job->name}; } $self->render(json => \@job_hashes); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/Test.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/Test.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Controller/Test.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Controller/Test.pm 2025-11-28 18:05:25.000000000 +0100 @@ -685,10 +685,9 @@ } # Take an job objects arrayref and prepare data structures for 'overview' -sub _prepare_job_results ($self, $all_jobs, $limit) { +sub _prepare_job_results ($self, $jobs, $job_ids) { my %archs; my %results; - my @job_ids; my $aggregated = { none => 0, passed => 0, @@ -699,25 +698,8 @@ running => 0, unknown => 0 }; - my $preferred_machines = _calculate_preferred_machines($all_jobs); - - # read parameter for additional filtering - my $failed_modules = $self->param_hash('failed_modules'); - my $states = $self->param_hash('state'); - my $results = $self->param_hash('result'); - my $archs = $self->param_hash('arch'); - my $machines = $self->param_hash('machine'); - - my @jobs = grep { - (not $states or $states->{$_->state}) - and (not $results or $results->{$_->result}) - and (not $archs or $archs->{$_->ARCH}) - and (not $machines or $machines->{$_->MACHINE}) - and (not $failed_modules or $_->result eq OpenQA::Jobs::Constants::FAILED) - } @$all_jobs; - my $limit_exceeded = @jobs >= $limit; - @jobs = @jobs[0 .. ($limit - 1)] if $limit_exceeded; - my @jobids = map { $_->id } @jobs; + my @jobs = $jobs->all; + my $preferred_machines = _calculate_preferred_machines(\@jobs); # prefetch the number of available labels for those jobs my $schema = $self->schema; @@ -726,7 +708,7 @@ # prefetch test suite names from job settings my $job_settings = $schema->resultset('JobSettings') - ->search({job_id => {-in => [map { $_->id } @jobs]}, key => {-in => [qw(JOB_DESCRIPTION TEST_SUITE_NAME)]}}); + ->search({job_id => {-in => $job_ids}, key => {-in => [qw(JOB_DESCRIPTION TEST_SUITE_NAME)]}}); my %settings_by_job_id; for my $js ($job_settings->all) { $settings_by_job_id{$js->job_id}->{$js->key} = $js->value; @@ -740,12 +722,13 @@ = $schema->resultset('TestSuites')->search({name => \%desc_args}, {columns => [qw(name description)]}); my %descriptions = map { $_->name => $_->description } @descriptions; - my $failed_modules_by_job = $self->_fetch_failed_modules_by_jobs(\@jobids); - my ($children_by_job, $parents_by_job) = $self->_fetch_dependencies_by_jobs(\@jobids); + my $failed_modules_by_job = $self->_fetch_failed_modules_by_jobs($job_ids); + my ($children_by_job, $parents_by_job) = $self->_fetch_dependencies_by_jobs($job_ids); foreach my $job (@jobs) { my $id = $job->id; my $result = $job->overview_result( - $comment_data, $aggregated, $failed_modules, + $comment_data, $aggregated, + $self->param_hash('failed_modules'), $failed_modules_by_job->{$id} || [], $self->param('todo')) or next; my $test = $job->TEST; @@ -780,14 +763,11 @@ $results{$distri}{$version}{$flavor}{$test} //= {}; $results{$distri}{$version}{$flavor}{$test}{$arch} = $result; - # populate job IDs for adding multiple comments - push @job_ids, $id; - # add description my $description = $settings_by_job_id{$id}->{JOB_DESCRIPTION} // $descriptions{$test_suite_names{$id}}; $results{$distri}{$version}{$flavor}{$test}{description} //= $description; } - return ($limit_exceeded, \%archs, \%results, \@job_ids, $aggregated); + return (\%archs, \%results, $aggregated); } sub _prepare_groupids ($self) { @@ -857,32 +837,26 @@ my $validation = $self->validation; $validation->optional('t')->datetime; my $until = $validation->param('t'); - my %stash = ( - # build, version, distri are not mandatory and therefore not - # necessarily come from the search args so they can be undefined. - build => ref $search_args->{build} eq 'ARRAY' ? join(',', @{$search_args->{build}}) : $search_args->{build}, - version => $search_args->{version}, - distri => $search_args->{distri}, - groups => $groups, - until => $until, - parallel_children_collapsable_results_sel => $config->{global}->{parallel_children_collapsable_results_sel}, - ); - my @jobs = $self->schema->resultset('Jobs')->complex_query(%$search_args)->latest_jobs($until); - - my $limit = $config->{misc_limits}->{tests_overview_max_jobs}; - (my $limit_exceeded, $stash{archs}, $stash{results}, $stash{job_ids}, $stash{aggregated}) - = $self->_prepare_job_results(\@jobs, $limit); + $search_args->{until} = $until; + my $distri = $search_args->{distri}; + my $version = $search_args->{version}; + my $jobs_rs = $self->schema->resultset('Jobs'); + my $latest_job_ids = $jobs_rs->complex_query_latest_ids(%$search_args); + my $limit = $search_args->{limit}; # one more than actual limit so we can check whether the limit was exceeded + my $exceeded_limit = @$latest_job_ids >= $limit ? $limit - 1 : 0; + my $jobs = $jobs_rs->latest_jobs_from_ids($latest_job_ids, $limit); + my ($archs, $results, $aggregated) = $self->_prepare_job_results($jobs, $latest_job_ids); # determine distri/version from job results if not explicitly specified via search args - my @distris = keys %{$stash{results}}; + my @distris = keys %$results; my $formatted_distri; my $formatted_version; my $only_distri = scalar @distris == 1; - if (!defined $stash{distri} && $only_distri) { + if (!defined $distri && $only_distri) { $formatted_distri = $distris[0]; - if (!defined $stash{version}) { - my @versions = keys %{$stash{results}->{$formatted_distri}}; - $formatted_version = $versions[0] if (scalar @versions == 1); + if (!defined $version) { + my @versions = keys %{$results->{$formatted_distri}}; + $formatted_version = $versions[0] if scalar @versions == 1; } } @@ -899,18 +873,28 @@ _add_distri_and_version_to_summary(\@summary_parts, $formatted_distri, $formatted_version, 1); # add distri and version from query parameters as regular strings - _add_distri_and_version_to_summary(\@summary_parts, $stash{distri}, $stash{version}, 0); + _add_distri_and_version_to_summary(\@summary_parts, $distri, $version, 0); } - $self->stash( - %stash, + my %stash = ( + # build, version, distri are not mandatory and therefore not + # necessarily come from the search args so they can be undefined. + build => ref $search_args->{build} eq 'ARRAY' ? join(',', @{$search_args->{build}}) : $search_args->{build}, + distri => $distri, + version => $version, + groups => $groups, + archs => $archs, + results => $results, + aggregated => $aggregated, + job_ids => $latest_job_ids, + until => $until, + parallel_children_collapsable_results_sel => $config->{global}->{parallel_children_collapsable_results_sel}, summary_parts => \@summary_parts, only_distri => $only_distri, - limit_exceeded => $limit_exceeded ? $limit : undef + limit_exceeded => $exceeded_limit, ); - $self->respond_to( - json => {json => \%stash}, - html => {template => 'test/overview'}); + $self->stash(\%stash); + $self->respond_to(json => {json => \%stash}, html => {template => 'test/overview'}); } sub _get_latest_job ($self) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/AMQP.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/AMQP.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/AMQP.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/AMQP.pm 2025-11-28 18:05:25.000000000 +0100 @@ -28,6 +28,8 @@ sub register ($self, $app, @args) { $self->{app} = $app; $self->{config} = $app->config; + my $config = $self->{config}->{amqp}; + $config->{enabled} = 1; # Needed for reloading the plugin later in the forked process Mojo::IOLoop->singleton->next_tick( sub { # register for events @@ -56,30 +58,39 @@ # create publisher and keep reference to avoid early destruction log_debug("Sending AMQP event: $topic"); my $config = $self->{config}->{amqp}; - my $url = Mojo::URL->new($config->{url}); - $url->query({exchange => $config->{exchange}}); + my $unsanitized_url = Mojo::URL->new($config->{url}); + $unsanitized_url->query({exchange => $config->{exchange}}); # append optional parameters - $url->query([cacertfile => $config->{cacertfile}]) if ($config->{cacertfile}); - $url->query([certfile => $config->{certfile}]) if ($config->{certfile}); - $url->query([keyfile => $config->{keyfile}]) if ($config->{keyfile}); - $url = $url->to_unsafe_string; - my $publisher = Mojo::RabbitMQ::Client::Publisher->new(url => $url); + $unsanitized_url->query([cacertfile => $config->{cacertfile}]) if ($config->{cacertfile}); + $unsanitized_url->query([certfile => $config->{certfile}]) if ($config->{certfile}); + $unsanitized_url->query([keyfile => $config->{keyfile}]) if ($config->{keyfile}); + my $url = $unsanitized_url->clone; + $unsanitized_url = $unsanitized_url->to_unsafe_string; + my $publisher = Mojo::RabbitMQ::Client::Publisher->new(url => $unsanitized_url); log_debug("AMQP URL: $url"); $remaining_attempts //= $config->{publish_attempts}; $retry_delay //= $config->{publish_retry_delay}; - $publisher->publish_p($event_data, $headers, routing_key => $topic)->then(sub { log_debug "$topic published" }) - ->catch( + $publisher->publish_p($event_data, $headers, routing_key => $topic)->then( + sub { + log_debug "$topic published"; + OpenQA::Events->singleton->emit('amqp_handled'); + } + )->catch( sub ($error) { my $left = looks_like_number $remaining_attempts && $remaining_attempts > 1 ? $remaining_attempts - 1 : 0; my $delay = $retry_delay * $config->{publish_retry_delay_factor}; my ($event_id, $job_id) = ($event_data->{id} // 'none', $event_data->{job_id}); my $additional_info = $job_id ? ", job ID: $job_id" : ''; my $log_msg = "Publishing $topic failed: $error (event ID: $event_id$additional_info, $left attempts left)"; - return log_error $log_msg unless $left; my $retry_function = sub ($loop) { $self->publish_amqp($topic, $event_data, $headers, $left, $delay) }; - log_info $log_msg; - Mojo::IOLoop->timer($retry_delay => $retry_function) if $left; + if ($left) { + log_info $log_msg; + Mojo::IOLoop->timer($retry_delay => $retry_function); + return; + } + OpenQA::Events->singleton->emit('amqp_handled'); + return log_error $log_msg; })->finally(sub { undef $publisher }); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/Helpers.pm new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/Helpers.pm --- old/openQA-5.1763743683.1da97aa2/lib/OpenQA/WebAPI/Plugin/Helpers.pm 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/lib/OpenQA/WebAPI/Plugin/Helpers.pm 2025-11-28 18:05:25.000000000 +0100 @@ -11,7 +11,7 @@ use OpenQA::Events; use OpenQA::Jobs::Constants qw(EXECUTION_STATES PRE_EXECUTION_STATES ABORTED_RESULTS FAILED NOT_COMPLETE_RESULTS); use Text::Glob qw(glob_to_regex_string); -use List::Util qw(any); +use List::Util qw(any min); use Feature::Compat::Try; sub register ($self, $app, $config) { @@ -338,6 +338,9 @@ $app->helper('reply.validation_error' => \&_validation_error); $app->helper(compose_job_overview_search_args => \&_compose_job_overview_search_args); + $app->helper(every_non_empty_param => \&_every_non_empty_param); + $app->helper(compute_overview_filtering_params => \&_compute_overview_filtering_params); + $app->helper(groups_for_globs => \&_groups_for_globs); $app->helper(param_hash => \&_param_hash); $app->helper( @@ -407,10 +410,9 @@ $v->optional('groupid')->num(0, undef); $v->optional('modules', 'comma_separated'); $v->optional('flavor', 'comma_separated'); - $v->optional('limit', 'not_empty')->num(0, undef); + $v->optional('limit', 'not_empty')->num(1, undef); # add simple query params to search args - $search_args{limit} = $v->param('limit') if $v->is_valid('limit'); for my $arg (qw(distri version flavor test)) { next unless $v->is_valid($arg); my @params = @{$v->every_param($arg) // []}; @@ -480,6 +482,10 @@ } } + # limit results to return one row more than the configured/specified limit so we know when the limit is exceeded + my $configured_limit = $c->app->config->{misc_limits}->{tests_overview_max_jobs}; + $search_args{limit} = ($v->is_valid('limit') ? min($configured_limit, $v->param('limit')) : $configured_limit) + 1; + # exclude jobs which are already cloned by setting scope for OpenQA::Jobs::complex_query() $search_args{scope} = 'current'; @@ -495,9 +501,31 @@ # allow filtering by comment text if (my $c = $v->param('comment')) { $search_args{comment_text} = $c } + # allow filtering by several job settings + $search_args{filters} = $c->compute_overview_filtering_params; + return (\%search_args, \@groups); } +sub _every_non_empty_param ($c, $param_key) { + [map { split ',', $_ } @{$c->every_param($param_key)}] +} + +sub _compute_overview_filtering_params ($c) { + my $states = $c->every_non_empty_param('state'); + my $results = $c->every_non_empty_param('result'); + my $archs = $c->every_non_empty_param('arch'); + my $machines = $c->every_non_empty_param('machine'); + my $failed_modules = $c->every_non_empty_param('failed_modules'); + my %filters; + $filters{state} = {-in => $states} if @$states; + $filters{result} = {-in => $results} if @$results; + $filters{result} = FAILED if @$failed_modules; + $filters{ARCH} = {-in => $archs} if @$archs; + $filters{MACHINE} = {-in => $machines} if @$machines; + return [\%filters]; +} + sub _groups_for_globs ($c) { my $v = $c->validation; $v->optional($_, 'not_empty') for qw(group_glob not_group_glob); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/script/openqa-enqueue-scheduled-product-cleanup new/openQA-5.1764349525.ffb59486/script/openqa-enqueue-scheduled-product-cleanup --- old/openQA-5.1763743683.1da97aa2/script/openqa-enqueue-scheduled-product-cleanup 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/script/openqa-enqueue-scheduled-product-cleanup 2025-11-28 18:05:25.000000000 +0100 @@ -0,0 +1,3 @@ +#!/bin/sh -e +[ "$1" = "-h" ] || [ "$1" = "--help" ] && echo "Enqueue openQA scheduled product cleanup" && exit +exec "$(dirname "$0")"/openqa minion -m production job -e limit_scheduled_products "$@" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.service new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.service --- old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.service 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.service 2025-11-28 18:05:25.000000000 +0100 @@ -0,0 +1,9 @@ +[Unit] +Description=openQA enqueue database scheduled product cleanup task +After=postgresql.service openqa-setup-db.service +Wants=openqa-setup-db.service + +[Service] +Type=oneshot +User=geekotest +ExecStart=/usr/share/openqa/script/openqa-enqueue-scheduled-product-cleanup diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.timer new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.timer --- old/openQA-5.1763743683.1da97aa2/systemd/openqa-enqueue-scheduled-product-cleanup.timer 1970-01-01 01:00:00.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/systemd/openqa-enqueue-scheduled-product-cleanup.timer 2025-11-28 18:05:25.000000000 +0100 @@ -0,0 +1,9 @@ +[Unit] +Description=Enqueues a scheduled product cleanup task for the openQA database every day. + +[Timer] +OnCalendar=daily +Persistent=true + +[Install] +WantedBy=timers.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/10-jobs.t new/openQA-5.1764349525.ffb59486/t/10-jobs.t --- old/openQA-5.1763743683.1da97aa2/t/10-jobs.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/10-jobs.t 2025-11-28 18:05:25.000000000 +0100 @@ -19,10 +19,11 @@ use OpenQA::Test::Case; use Test::MockModule 'strict'; use Test::Mojo; +use Test::Output; use Test::Warnings qw(:report_warnings warning); -use Mojo::File 'path'; +use Mojo::File qw(path tempdir); use Mojo::JSON qw(decode_json encode_json); -use OpenQA::Test::Utils qw(perform_minion_jobs); +use OpenQA::Test::Utils qw(perform_minion_jobs mock_io_loop); use OpenQA::Test::TimeLimit '30'; binmode(STDOUT, ':encoding(UTF-8)'); @@ -80,13 +81,17 @@ is $jobs->latest_build(version => 'Factory', distri => 'opensuse'), '0048@0815', 'latest for non-integer build'; is $jobs->latest_build(version => '13.1', distri => 'opensuse'), '0091', 'latest for different version differs'; - my @latest = $jobs->latest_jobs; - my @ids = map { $_->id } @latest; + my $ids = $jobs->complex_query_latest_ids; # These two jobs have later clones in the fixture set, so should not appear - ok(grep(!/^(99962|99945)$/, @ids), 'jobs with later clones do not show up in latest jobs'); + ok grep(!/^(99962|99945)$/, @$ids), 'jobs with later clones do not show up in latest jobs'; # These are the later clones, they should appear - ok(grep(/^99963$/, @ids), 'cloned jobs appear as latest job'); - ok(grep(/^99946$/, @ids), 'cloned jobs appear as latest job (2nd)'); + ok grep(/^99963$/, @$ids), 'cloned jobs appear as latest job'; + ok grep(/^99946$/, @$ids), 'cloned jobs appear as latest job (2nd)'; + + my $filtered_ids = $jobs->complex_query_latest_ids(filters => [{result => {-in => [FAILED, PASSED]}}]); + ok grep(!/^(99962|99945)$/, @$filtered_ids), 'jobs with later clones still not present'; + ok grep(/^99946$/, @$filtered_ids), 'failed/passed job still returned'; + ok grep(!/^99963$/, @$filtered_ids), 'running job filtered out'; }; @@ -899,7 +904,8 @@ my $finalize_job_count_before = @{$get_jobs->('finalize_job_results')}; $job->update({state => SCHEDULED, result => NONE}); $job->done(result => FAILED); - perform_minion_jobs($minion); + stdout_like { perform_minion_jobs($minion) } qr/Job \d+ duplicated as \d+/, + 'check debug message from auto_duplicate'; is $jobs->count, $jobs_nr + 2, 'job retriggered as it FAILED (with retry)'; $job->update; $job->discard_changes; @@ -931,6 +937,58 @@ is $lastest_job->latest_job->id, $lastest_job->id, 'found the latest job from latest job itself'; }; +subtest 'AMQP event emission for job restarts within Minion tasks' => sub { + my $plugin_mock = Test::MockModule->new('OpenQA::WebAPI::Plugin::AMQP'); + my $conf = "[global]\nplugins=AMQP\n[amqp]\npublish_attempts = 2\npublish_retry_delay = 0\n"; + my $tempdir = tempdir; + path($ENV{OPENQA_CONFIG} = $tempdir)->make_path->child('openqa.ini')->spew($conf); + my %published; + my @event_body; + my $io_loop_mock = Test::MockModule->new('Mojo::IOLoop'); + $io_loop_mock->redefine(start => sub { }); + $plugin_mock->redefine( + publish_amqp => sub ($self, $topic, $data) { + $published{$topic} = $data; + Mojo::IOLoop->next_tick(sub { OpenQA::Events->singleton->emit('amqp_handled'); }); + }); + + my $events_mock = Test::MockModule->new('OpenQA::Events'); + $events_mock->redefine( + emit => sub ($self, $type, $args) { + if ($type eq 'openqa_job_restart') { + @event_body = ($type, $args); + } + $events_mock->original('emit')->($self, $type, $args); + }); + # use a new app; otherwise it runs slowly, maybe trying to run none-mocked stuff + my $t = Test::Mojo->new('OpenQA::WebAPI'); + my $minion = $t->app->minion; + is $t->app->config->{amqp}->{enabled}, 1, 'AMQP enabled from config file'; + + my %retry_settings = %settings; + $retry_settings{TEST} = 'test_amqp_restart'; + $retry_settings{RETRY} = '1'; + my $job = $jobs->create_from_settings(\%retry_settings); + $job->discard_changes; + my $job_id = $job->id; + + %published = (); + $job->done(result => FAILED); + stdout_like { perform_minion_jobs($minion) } qr/Job \d+ duplicated as \d+/; + is scalar keys %published, 1, 'exactly one job restart event emitted'; + my $event = $published{'suse.openqa.job.restart'}; + is $event->{id}, $job_id, 'event contains original job ID'; + is $event->{auto}, 1, 'event marked as auto restart'; + my ($user_id, $connection, $type, $data) = @{$event_body[1]}; + is $user_id, undef, 'user_id is undef for Minion restart'; + is $connection, undef, 'connection is undef for Minion restart'; + is $type, 'openqa_job_restart', 'type matches event type'; + is_deeply $data, $event, 'published event equals emitted event'; + ok exists $data->{result}->{$job_id}, 'old job id is in result'; + $job->discard_changes; + is $job->clone_id, $data->{result}->{$job_id}, 'clone_id points to reported id'; +}; + subtest '"race" between status updates and stale job detection' => sub { my $job = $jobs->create({TEST => 'test-job'}); is_deeply $job->update_status({}), {result => 0}, 'status update rejected for scheduled job'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/10-tests_overview.t new/openQA-5.1764349525.ffb59486/t/10-tests_overview.t --- old/openQA-5.1763743683.1da97aa2/t/10-tests_overview.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/10-tests_overview.t 2025-11-28 18:05:25.000000000 +0100 @@ -151,10 +151,8 @@ }; subtest 'limit parameter' => sub { - $t->get_ok( - '/tests/overview?distri=opensuse&version=Factory&limit=2&limit=2', - 'no database error when specifying more than one limit' - ); + $t->get_ok('/tests/overview?distri=opensuse&version=Factory&build=0048&limit=2&limit=2', + 'no database error when specifying more than one limit'); is $t->tx->res->dom->find('table.overview td.name')->size, 2, 'number of jobs limited'; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/23-amqp.t new/openQA-5.1764349525.ffb59486/t/23-amqp.t --- old/openQA-5.1763743683.1da97aa2/t/23-amqp.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/23-amqp.t 2025-11-28 18:05:25.000000000 +0100 @@ -276,7 +276,8 @@ $amqp->register($app); subtest 'amqp_publish call without headers' => sub { - $amqp->publish_amqp('some.topic', 'some message'); + combined_like { $amqp->publish_amqp('some.topic', 'some message') } qr/AMQP URL: amqp:\/\/localhost:5672/, + 'URL is logged without credentials'; is($last_publisher->url, 'amqp://guest:guest@localhost:5672/?exchange=pubsub', 'url specified'); is($published{body}, 'some message', 'message body correctly passed'); is_deeply($published{headers}, {}, 'headers is empty hashref'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/39-scheduled_products-table.t new/openQA-5.1764349525.ffb59486/t/39-scheduled_products-table.t --- old/openQA-5.1763743683.1da97aa2/t/39-scheduled_products-table.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/39-scheduled_products-table.t 2025-11-28 18:05:25.000000000 +0100 @@ -16,6 +16,7 @@ use OpenQA::Test::TimeLimit '6'; use OpenQA::Test::Case; use OpenQA::Utils; +use DateTime; OpenQA::Test::Case->new->init_data; my $t = Test::Mojo->new('OpenQA::WebAPI'); @@ -148,4 +149,12 @@ is @ids, 2, 'two jobs created'; }; +subtest 'cleanup' => sub { + $scheduled_products->search({id => 1})->update({t_created => DateTime->now->subtract(days => 35)}); + $scheduled_products->delete_expired_entries; + ok !$scheduled_products->find(1), 'old scheduled product without jobs deleted'; + ok $scheduled_products->find(2), 'new scheduled product without jobs still present)'; + ok $scheduled_products->find(3), 'scheduled product with jobs still present'; +}; + done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/41-audit-log.t new/openQA-5.1764349525.ffb59486/t/41-audit-log.t --- old/openQA-5.1763743683.1da97aa2/t/41-audit-log.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/41-audit-log.t 2025-11-28 18:05:25.000000000 +0100 @@ -75,52 +75,52 @@ # note: The tested time constraints are defined in t/data/41-audit-log/openqa.ini. -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'all events considered recent enough to be kept'); assume_all_events_before_x_days(15); assume_events_being_deleted(1080, 1081); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'other events deleted after 10 days'); assume_all_events_before_x_days(25); assume_events_being_deleted(1000); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'startup events deleted after 20 days'); assume_all_events_before_x_days(35); assume_events_being_deleted(1030, 1031); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'job group events deleted after 30 days'); assume_all_events_before_x_days(45); assume_events_being_deleted(1020, 1021); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'job template events deleted after 40 days'); assume_all_events_before_x_days(55); assume_events_being_deleted(1010, 1011); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'table events deleted after 50 days'); assume_all_events_before_x_days(65); assume_events_being_deleted(1060, 1061); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'iso events deleted after 60 days'); assume_all_events_before_x_days(75); assume_events_being_deleted(1050); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'user events deleted after 70 days'); assume_all_events_before_x_days(85); assume_events_being_deleted(1070, 1071); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'asset events deleted after 80 days'); assume_all_events_before_x_days(95); assume_events_being_deleted(1040); -$events->delete_entries_exceeding_storage_duration; +$events->delete_expired_entries; is_deeply(all_events_ids, [sort keys %fake_events], 'needle events deleted after 90 days'); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/api/04-jobs.t new/openQA-5.1764349525.ffb59486/t/api/04-jobs.t --- old/openQA-5.1763743683.1da97aa2/t/api/04-jobs.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/api/04-jobs.t 2025-11-28 18:05:25.000000000 +0100 @@ -278,7 +278,8 @@ subtest 'limit parameter' => sub { $query->query(build => '0048', limit => 1); $t->get_ok($query->path_query)->status_is(200); - is(scalar(@{$t->tx->res->json}), 1, 'Expect only one job entry'); + my $json = $t->tx->res->json; + is scalar @$json, 1, 'Expect only one job entry' or always_explain $json; $t->json_is('/0/id' => 99939, 'Check correct order'); }; }; @@ -1008,8 +1009,14 @@ $t->post_ok('/api/v1/jobs', form => \%jobs_post_params)->status_is(200); $t->get_ok('/api/v1/jobs/' . $t->tx->res->json->{id})->status_is(200); $t->json_is('/job/priority', 50 + $max / 100, 'increased prio value'); - my $limits = OpenQA::App->singleton->config->{misc_limits}; + local $jobs_post_params{TIMEOUT_SCALE} = 2; + $t->post_ok('/api/v1/jobs', form => \%jobs_post_params)->status_is(200); + $t->get_ok('/api/v1/jobs/' . $t->tx->res->json->{id})->status_is(200); + $t->json_is('/job/priority', 50 + $max * 2 / 100, 'increased prio value with TIMEOUT_SCALE'); + delete $jobs_post_params{TIMEOUT_SCALE}; + + my $limits = OpenQA::App->singleton->config->{misc_limits}; $limits->{max_job_time_prio_scale} = 10; $t->post_ok('/api/v1/jobs', form => \%jobs_post_params)->status_is(200); $t->get_ok('/api/v1/jobs/' . $t->tx->res->json->{id})->status_is(200); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/config.t new/openQA-5.1764349525.ffb59486/t/config.t --- old/openQA-5.1763743683.1da97aa2/t/config.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/config.t 2025-11-28 18:05:25.000000000 +0100 @@ -192,6 +192,7 @@ worker_limit_retry_delay => ONE_HOUR / 4, mcp_max_result_size => 500000, max_job_time_prio_scale => 100, + scheduled_product_min_storage_duration => 34, }, archiving => { archive_preserved_important_jobs => 0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/t/ui/10-tests_overview.t new/openQA-5.1764349525.ffb59486/t/ui/10-tests_overview.t --- old/openQA-5.1763743683.1da97aa2/t/ui/10-tests_overview.t 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/t/ui/10-tests_overview.t 2025-11-28 18:05:25.000000000 +0100 @@ -487,8 +487,9 @@ element_not_present("#$_") for qw(flavor_DVD_arch_i586 flavor_GNOME-Live_arch_i686 flavor_NET_arch_x86_64); is element_prop('filter-machine'), 'uefi', 'machine filter still visible in form'; - my @job_rows = map { $_->get_text } @{$driver->find_elements('#content tbody tr')}; - is_deeply \@job_rows, ['kde@uefi'], 'only the job with machine uefi is shown' or always_explain \@job_rows; + my $get_job_ids = qq|return Array.from(document.querySelectorAll('span[id^="res-"]')).map(e => e.id.substr(4))|; + my $job_ids = $driver->execute_script($get_job_ids); + is_deeply $job_ids, ['99982'], 'only the job with machine uefi is shown' or always_explain $job_ids; $driver->find_element('#filter-panel .card-header')->click(); $driver->find_element('#filter-machine')->clear(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1763743683.1da97aa2/templates/webapi/test/overview.html.ep new/openQA-5.1764349525.ffb59486/templates/webapi/test/overview.html.ep --- old/openQA-5.1763743683.1da97aa2/templates/webapi/test/overview.html.ep 2025-11-21 17:48:03.000000000 +0100 +++ new/openQA-5.1764349525.ffb59486/templates/webapi/test/overview.html.ep 2025-11-28 18:05:25.000000000 +0100 @@ -44,9 +44,9 @@ % if ($allow_commenting) { <button type="button" class="btn btn-secondary btn-circle btn-sm trigger-edit-button" onclick="showAddCommentsDialog()" title="Restart or comment jobs"> <span class="fa-stack group-comment-icon-stack"> - <i class="fa fa-comment fa-stack-2x text-danger-info" aria-hidden="true"></i> - <i class="fa fa-undo fa-stack-1x" aria-hidden="true"></i> - </span> + <i class="fa fa-comment fa-stack-2x text-danger-info" aria-hidden="true"></i> + <i class="fa fa-undo fa-stack-1x" aria-hidden="true"></i> + </span> </button> % } % my @badges = qw(success secondary warning danger info primary light light); ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.95quJ0/_old 2025-12-01 11:13:24.202714944 +0100 +++ /var/tmp/diff_new_pack.95quJ0/_new 2025-12-01 11:13:24.206715113 +0100 @@ -1,5 +1,5 @@ name: openQA -version: 5.1763743683.1da97aa2 -mtime: 1763743683 -commit: 1da97aa28ce09ffbdf8755234076efc7641d3b45 +version: 5.1764349525.ffb59486 +mtime: 1764349525 +commit: ffb5948677625e9c9e3c79d59e74a8289bac40b0
