Skip to content

Commit 4a5470e

Browse files
committed
Refactor Azure diagnostic log retrieval and improve error handling
Centralize the logic for fetching Azure VM boot diagnostics in azure_cli.pm by enhancing az_vm_diagnostic_log_get with timeout support and better error reporting. Refactor qesap_az_diagnostic_log to use this centralized function, reducing code duplication. Update unit tests in 15_qesap_azure.t and 21_sles4sap_azure_cli.t to reflect these changes and cover new failure scenarios.
1 parent 59b179d commit 4a5470e

File tree

5 files changed

+159
-119
lines changed

5 files changed

+159
-119
lines changed

lib/sles4sap/azure_cli.pm

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,19 +1427,39 @@ Return a list of diagnostic file paths on the JumpHost
14271427
14281428
=item B<resource_group> - existing resource group where to search for a specific VM
14291429
1430+
=item B<timeout> - max expected execution time for a single az command - default 240sec
1431+
1432+
=item B<verbose> - use tee to print the output also on the terminal while redirecting to file. Default 0
1433+
14301434
=back
14311435
=cut
14321436

14331437
sub az_vm_diagnostic_log_get(%args) {
14341438
croak("Argument < resource_group > missing") unless $args{resource_group};
14351439

1440+
my $timeout = $args{timeout} // 240;
14361441
my @diagnostic_log_files;
14371442
my $vm_data = az_vm_list(resource_group => $args{resource_group}, query => '[].{id:id,name:name}');
14381443
my $az_get_logs_cmd = 'az vm boot-diagnostics get-boot-log --ids';
1444+
my $ret;
1445+
my $err;
1446+
my $redirect = $args{verbose} ? '|& tee' : '&>';
1447+
my $boot_diagnostics_log;
14391448
foreach (@{$vm_data}) {
1440-
my $boot_diagnostics_log = '/tmp/boot-diagnostics_' . $_->{name} . '.txt';
1441-
push @diagnostic_log_files, $boot_diagnostics_log unless
1442-
script_run(join(' ', $az_get_logs_cmd, $_->{id}, '|&', 'tee', $boot_diagnostics_log));
1449+
$boot_diagnostics_log = '/tmp/boot-diagnostics_' . $_->{name} . '.txt';
1450+
eval {
1451+
$ret = script_run(
1452+
join(' ', $az_get_logs_cmd, $_->{id}, $redirect, $boot_diagnostics_log),
1453+
timeout => $timeout);
1454+
};
1455+
$err = $@;
1456+
if ($err || !defined $ret || $ret != 0) {
1457+
my $detail = ($err && $err =~ /\S/) ? "$err" : "Exit code: $ret";
1458+
my $fail_msg = "Failed to get boot diagnostics for $_->{name}: $detail";
1459+
record_info('Diag Warn', $fail_msg, result => 'fail');
1460+
next;
1461+
}
1462+
push(@diagnostic_log_files, $boot_diagnostics_log);
14431463
}
14441464
return @diagnostic_log_files;
14451465
}

lib/sles4sap/ipaddr2.pm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1712,7 +1712,8 @@ Collect logs from the cloud infrastructure
17121712

17131713
sub ipaddr2_deployment_logs {
17141714
my @diagnostic_log_files = az_vm_diagnostic_log_get(
1715-
resource_group => ipaddr2_azure_resource_group());
1715+
resource_group => ipaddr2_azure_resource_group(),
1716+
verbose => 1); #TODO remove it
17161717
while (my $file = pop @diagnostic_log_files) {
17171718
upload_logs($file, failok => 1);
17181719
}

lib/sles4sap/qesap/azure.pm

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ use Carp qw(croak);
3333
use Mojo::JSON qw(decode_json);
3434
use Exporter 'import';
3535
use File::Basename;
36+
use testapi;
37+
use mmapi 'get_current_job_id';
3638
use sles4sap::azure_cli;
3739
use sles4sap::qesap::utils;
38-
use mmapi 'get_current_job_id';
39-
use testapi;
4040

4141
our @EXPORT = qw(
4242
qesap_az_get_resource_group
@@ -325,33 +325,12 @@ Return a list of diagnostic file paths on the JumpHost
325325
sub qesap_az_diagnostic_log {
326326
my (%args) = @_;
327327
my $fatal = $args{fatal} // 1;
328-
my $timeout = $args{timeout} // 240;
329-
my @diagnostic_log_files;
330328
my @failures;
331329

332-
my $rg = qesap_az_get_resource_group();
333-
my $vm_data = az_vm_list(resource_group => $rg, query => '[].{id:id,name:name}');
334-
my $az_get_logs_cmd = 'az vm boot-diagnostics get-boot-log --ids';
335-
336-
foreach (@{$vm_data}) {
337-
record_info('az vm boot-diagnostics json', "id: $_->{id} name: $_->{name}");
338-
my $boot_diagnostics_log = '/tmp/boot-diagnostics_' . $_->{name} . '.txt';
339-
340-
my $ret;
341-
eval {
342-
$ret = script_run("$az_get_logs_cmd $_->{id} &> $boot_diagnostics_log", timeout => $timeout);
343-
};
344-
my $error = $@;
345-
346-
if ($error || !defined $ret || $ret != 0) {
347-
my $detail = ($error && $error =~ /\S/) ? "$error" : "Exit code: $ret";
348-
my $fail_msg = "Failed to get boot diagnostics for $_->{name}: $detail";
349-
record_info('Diag Warn', $fail_msg, result => 'fail');
350-
push @failures, $fail_msg;
351-
next;
352-
}
353-
push(@diagnostic_log_files, $boot_diagnostics_log);
354-
}
330+
my @diagnostic_log_files = az_vm_diagnostic_log_get(
331+
resource_group => qesap_az_get_resource_group(),
332+
timeout => $args{timeout} // 240,
333+
verbose => 1); #TODO remove it
355334

356335
if (@failures && $fatal) {
357336
die "Fatal Error:\n" . join("\n", @failures);

t/15_qesap_azure.t

Lines changed: 55 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -325,46 +325,72 @@ subtest '[qesap_az_diagnostic_log] no VMs' => sub {
325325
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
326326
my @calls;
327327
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
328+
$qesap->redefine(az_vm_diagnostic_log_get => sub { push @calls, {@_}; return (); });
329+
$qesap->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
330+
331+
my @log_files = qesap_az_diagnostic_log();
332+
333+
ok((any { $_->{resource_group} eq 'DENTIST' } @calls), 'Proper resource group in vm list');
334+
#ok((any { $_->{query} =~ /id:id,name:name/ } @calls), 'Proper query in vm list');
335+
ok((scalar @log_files == 0), 'No returned logs');
336+
};
337+
338+
subtest '[qesap_az_diagnostic_log] no VMs integration test' => sub {
339+
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
340+
my $azcli = Test::MockModule->new('sles4sap::azure_cli', no_auto => 1);
341+
my @calls;
342+
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
328343
$qesap->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
329344

330345
# Configure vm list to return no VMs
331-
$qesap->redefine(az_vm_list => sub { push @calls, {@_}; return []; });
346+
$azcli->redefine(script_output => sub { push @calls, $_[0]; return '[]'; });
332347

333348
my @log_files = qesap_az_diagnostic_log();
334349

335350
note("\n C--> " . join("\n C--> ", @calls));
336-
ok((any { $_->{resource_group} eq 'DENTIST' } @calls), 'Proper resource group in vm list');
337-
ok((any { $_->{query} =~ /id:id,name:name/ } @calls), 'Proper query in vm list');
351+
ok((any { /az vm list.*DENTIST.*/ } @calls), 'List all VMs in the resource group');
338352
ok((scalar @log_files == 0), 'No returned logs');
339353
};
340354

341355
subtest '[qesap_az_diagnostic_log] one VMs' => sub {
342356
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
343357
my @calls;
344358
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
359+
$qesap->redefine(az_vm_diagnostic_log_get => sub { push @calls, {@_}; return ('pink_coral.log'); });
345360
$qesap->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
346361

362+
my @log_files = qesap_az_diagnostic_log();
363+
364+
ok((scalar @log_files == 1), 'Exactly one returned logs for one VM');
365+
};
366+
367+
subtest '[qesap_az_diagnostic_log] one VMs integration test' => sub {
368+
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
369+
my $azcli = Test::MockModule->new('sles4sap::azure_cli', no_auto => 1);
370+
my @calls;
371+
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
372+
373+
$azcli->redefine(script_run => sub { push @calls, $_[0]; return 0; });
347374
# Configure vm list to return one VM
348-
$qesap->redefine(az_vm_list => sub {
349-
push @calls, {@_};
350-
return [{name => "NEMO", id => "MARLIN"}];
375+
$azcli->redefine(script_output => sub {
376+
push @calls, $_[0];
377+
return '[{"name":"NEMO", "id":"MARLIN"}]';
351378
});
352-
$qesap->redefine(script_run => sub { push @calls, $_[0]; return 0; });
379+
$azcli->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
353380

354381
my @log_files = qesap_az_diagnostic_log();
355382

356383
note("\n C--> " . join("\n C--> ", @calls));
357-
ok((any { $_->{resource_group} eq 'DENTIST' } @calls), 'Proper resource group in vm list');
358-
ok((any { /az vm boot-diagnostics get-boot-log.*/ } @calls), 'Proper base command for vm boot-diagnostics get-boot-log');
359-
ok((any { /.*--ids MARLIN.*/ } @calls), 'Proper id in boot-diagnostics');
360-
ok((any { /.*boot-diagnostics_NEMO.*/ } @calls), 'Proper output file in boot-diagnostics');
361384
ok((scalar @log_files == 1), 'Exactly one returned logs for one VM');
362385
};
363386

364387
subtest '[qesap_az_diagnostic_log] three VMs' => sub {
365388
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
366389
my @calls;
367390
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
391+
$qesap->redefine(az_vm_diagnostic_log_get => sub {
392+
push @calls, {@_};
393+
return ('pink_coral.log', 'blue_coral.log', 'white_coral.log'); });
368394
$qesap->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
369395

370396
# Configure vm list to return three VMs
@@ -378,10 +404,27 @@ subtest '[qesap_az_diagnostic_log] three VMs' => sub {
378404
});
379405
$qesap->redefine(script_run => sub { push @calls, $_[0]; return 0; });
380406

407+
my @log_files = qesap_az_diagnostic_log();
408+
ok((scalar @log_files == 3), 'Exactly three returned logs for three VMs');
409+
};
410+
411+
subtest '[qesap_az_diagnostic_log] three VMs integration test' => sub {
412+
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
413+
my $azcli = Test::MockModule->new('sles4sap::azure_cli', no_auto => 1);
414+
my @calls;
415+
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
416+
417+
$azcli->redefine(script_run => sub { push @calls, $_[0]; return 0; });
418+
# Configure vm list to return 3 VMs
419+
$azcli->redefine(script_output => sub {
420+
push @calls, $_[0];
421+
return '[{"name":"DORY", "id":"BLUE_TANG"},{"name":"BRUCE", "id":"GREAT_WHITE"},{"name":"CRUSH", "id":"SEA_TURTLE"}]';
422+
});
423+
$azcli->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
424+
381425
my @log_files = qesap_az_diagnostic_log();
382426

383427
note("\n C--> " . join("\n C--> ", @calls));
384-
ok((any { ref($_) eq 'HASH' && $_->{resource_group} eq 'DENTIST' } @calls), 'Proper resource group in vm list');
385428

386429
my %expected_vms = (
387430
DORY => "BLUE_TANG",
@@ -396,76 +439,4 @@ subtest '[qesap_az_diagnostic_log] three VMs' => sub {
396439
ok((scalar @log_files == 3), 'Exactly three returned logs for three VMs');
397440
};
398441

399-
subtest '[qesap_az_diagnostic_log] Partial Success (fatal = 0)' => sub {
400-
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
401-
my @record_calls;
402-
403-
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
404-
$qesap->redefine(az_vm_list => sub {
405-
return [
406-
{name => "DORY", id => "BLUE_TANG"},
407-
{name => "BRUCE", id => "GREAT_WHITE"},
408-
{name => "CRUSH", id => "SEA_TURTLE"}
409-
];
410-
});
411-
412-
$qesap->redefine(script_run => sub {
413-
my ($cmd) = @_;
414-
return ($cmd =~ /BRUCE/) ? 1 : 0;
415-
});
416-
$qesap->redefine(record_info => sub { push @record_calls, $_[0] if $_[0] eq 'Diag Warn'; });
417-
418-
my @log_files;
419-
lives_ok {
420-
@log_files = qesap_az_diagnostic_log(fatal => 0);
421-
} 'Fail silently';
422-
423-
is(scalar @log_files, 2, 'Return exactly 2 successful logs');
424-
ok((any { /boot-diagnostics_DORY.txt/ } @log_files), 'DORY log present');
425-
ok((any { /boot-diagnostics_CRUSH.txt/ } @log_files), 'CRUSH log present');
426-
ok(!(any { /boot-diagnostics_BRUCE.txt/ } @log_files), 'BRUCE log absent');
427-
is(scalar @record_calls, 1, 'One failure for BRUCE');
428-
};
429-
430-
subtest '[qesap_az_diagnostic_log] Cumulative Failures (fatal = 1)' => sub {
431-
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
432-
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
433-
$qesap->redefine(record_info => sub { 1; });
434-
435-
$qesap->redefine(az_vm_list => sub {
436-
return [
437-
{name => "NEMO", id => "MARLIN"},
438-
{name => "DORY", id => "BLUE_TANG"}
439-
];
440-
});
441-
442-
$qesap->redefine(script_run => sub { return 1; });
443-
throws_ok {
444-
qesap_az_diagnostic_log(fatal => 1);
445-
} qr/Fatal Error:.*NEMO.*DORY/s, 'Die as expected';
446-
};
447-
448-
subtest '[qesap_az_diagnostic_log] Mixed Error' => sub {
449-
my $qesap = Test::MockModule->new('sles4sap::qesap::azure', no_auto => 1);
450-
$qesap->redefine(qesap_az_get_resource_group => sub { return 'DENTIST'; });
451-
$qesap->redefine(record_info => sub { 1; });
452-
453-
$qesap->redefine(az_vm_list => sub {
454-
return [
455-
{name => "NEMO", id => "MARLIN"},
456-
{name => "DORY", id => "BLUE_TANG"}
457-
];
458-
});
459-
460-
$qesap->redefine(script_run => sub {
461-
my ($cmd) = @_;
462-
die "Timeout issue" if $cmd =~ /DORY/;
463-
return 1; # NEMO
464-
});
465-
466-
throws_ok {
467-
qesap_az_diagnostic_log(fatal => 1);
468-
} qr/Exit code: 1.*DORY: Timeout issue/s, 'Exitcode and timeout as expected';
469-
};
470-
471442
done_testing;

t/21_sles4sap_azure_cli.t

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -860,14 +860,83 @@ subtest '[az_vm_diagnostic_log_get]' => sub {
860860
push @calls, $_[0];
861861
# simulate 2 VM
862862
return '[{"id": "0001", "name": "Truffaldino"}, {"id": "0002", "name": "Mirandolina"}]'; });
863+
$azcli->redefine(script_run => sub { push @calls, $_[0]; return 0; });
864+
$azcli->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
865+
866+
my @ret = az_vm_diagnostic_log_get(resource_group => 'Arlecchino');
867+
868+
note("\n C--> " . join("\n C--> ", @calls));
869+
note("\n R--> " . join("\n R--> ", @ret));
870+
ok((any { /az vm list/ } @calls), 'Correct composition of the list command');
871+
ok((any { /az vm boot-diagnostics get-boot-log/ } @calls), 'Correct composition of the main command');
872+
ok((any { /--ids 0001.*Truffaldino\.txt/ } @calls), 'Correct composition of the --id for the first VM');
873+
ok((any { /--ids 0002.*Mirandolina\.txt/ } @calls), 'Correct composition of the --id for the second VM');
874+
ok((none { /.*tee.*/ } @calls), 'Correctly not use tee to redirect output to file');
875+
876+
ok scalar @ret == 2, "Two logs one for each VM";
877+
ok((any { /\/tmp\/boot-diagnostics_.*/ } @ret), 'Correct log filenames');
878+
};
879+
880+
subtest '[az_vm_diagnostic_log_get] verbose' => sub {
881+
my $azcli = Test::MockModule->new('sles4sap::azure_cli', no_auto => 1);
882+
my @calls;
883+
$azcli->redefine(script_output => sub {
884+
push @calls, $_[0];
885+
# simulate 2 VM
886+
return '[{"id": "0001", "name": "Truffaldino"}, {"id": "0002", "name": "Mirandolina"}]'; });
887+
$azcli->redefine(script_run => sub { push @calls, $_[0]; return 0; });
888+
$azcli->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
889+
890+
my @ret = az_vm_diagnostic_log_get(resource_group => 'Arlecchino', verbose => 1);
891+
892+
note("\n C--> " . join("\n C--> ", @calls));
893+
note("\n R--> " . join("\n R--> ", @ret));
894+
ok((any { /.*tee.*/ } @calls), 'Correctly use tee to redirect output to file');
895+
};
896+
897+
subtest '[az_vm_diagnostic_log_get] no VMs' => sub {
898+
my $azcli = Test::MockModule->new('sles4sap::azure_cli', no_auto => 1);
899+
my @calls;
900+
$azcli->redefine(script_output => sub {
901+
push @calls, $_[0];
902+
# simulate 2 VM
903+
return '[]'; });
863904
$azcli->redefine(script_run => sub { push @calls, $_[0]; });
905+
$azcli->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
864906

865-
az_vm_diagnostic_log_get(resource_group => 'Arlecchino');
907+
my @ret = az_vm_diagnostic_log_get(resource_group => 'Arlecchino');
866908

867-
note("\n --> " . join("\n --> ", @calls));
909+
note("\n C--> " . join("\n C--> ", @calls));
910+
note("\n R--> " . join("\n R--> ", @ret));
911+
ok((any { /az vm list/ } @calls), 'Correct composition of the list command');
912+
ok((none { /az vm boot-diagnostics get-boot-log/ } @calls), 'Correct composition of the main command');
913+
ok scalar @ret == 0, "No logs";
914+
};
915+
916+
subtest '[az_vm_diagnostic_log_get] one fail' => sub {
917+
my $azcli = Test::MockModule->new('sles4sap::azure_cli', no_auto => 1);
918+
my @calls;
919+
$azcli->redefine(script_output => sub {
920+
push @calls, $_[0];
921+
# simulate 2 VM
922+
return '[{"id": "0001", "name": "Truffaldino"}, {"id": "0002", "name": "Mirandolina"}]'; });
923+
$azcli->redefine(script_run => sub {
924+
my ($cmd) = @_;
925+
push @calls, $cmd;
926+
return ($cmd =~ /Mirandolina/) ? 1 : 0; });
927+
$azcli->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
928+
929+
my @ret = az_vm_diagnostic_log_get(resource_group => 'Arlecchino');
930+
931+
note("\n C--> " . join("\n C--> ", @calls));
932+
note("\n R--> " . join("\n R--> ", @ret));
933+
ok((any { /az vm list/ } @calls), 'Correct composition of the list command');
868934
ok((any { /az vm boot-diagnostics get-boot-log/ } @calls), 'Correct composition of the main command');
869-
ok((any { /--ids 0001.*tee.*Truffaldino\.txt/ } @calls), 'Correct composition of the --id for the first VM');
870-
ok((any { /--ids 0002.*tee.*Mirandolina\.txt/ } @calls), 'Correct composition of the --id for the second VM');
935+
ok((any { /--ids 0001.*Truffaldino\.txt/ } @calls), 'Correct composition of the --id for the first VM');
936+
ok((any { /--ids 0002.*Mirandolina\.txt/ } @calls), 'Correct composition of the --id for the second VM');
937+
938+
ok scalar @ret == 1, "One log for the non failing VM";
939+
ok((any { /\/tmp\/boot-diagnostics_.*/ } @ret), 'Correct log filenames');
871940
};
872941

873942
subtest '[az_storage_account_create]' => sub {

0 commit comments

Comments
 (0)