## @summary Internal library used to change the current state of the service ## @author anerudh@netapp.com, dl-nacl-dev@netapp.com ## most of this functionality is from lib/boot.pm package NACL::Transit::ChangeState; use base qw /NACL::Transit::StateDefinitions/; use base qw /NACL::Transit::RetrieveState/; use strict; use Tharn; use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use warnings; # Exceptions use NATE::BaseException qw/:try/; use NATE::Exceptions::Argument qw/:try/; use NACL::Transit::Exceptions::Internal qw/:try/; use NACL::Transit::Exceptions::TransitException qw/:try/; use NACL::Transit::Exceptions::RetriesExceeded qw/:try/; # send appropriate strings to go from state $from to state $to. # return the state reached if the transit suceeded. # Throw an exception if anything goes wrong while in one on the intermediate # states based on whether it is an ONTAP state when a NONONTAP state was # expected and vice-versa and if the number of retries have been exceeded while # trying to get to the specified state. sub transit_to_nopowercycle ( $$$@ ) { my ($self, %args) = @_; $Log->enter() if $may_enter; my ($conn, $to) = ($self->{conn}, $args{to}); if (! "NACL::Transit::StateDefinitions::$to"->isa('NACL::Transit::StateDefinitions::STATE')) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("$to is an unknown boot state") } my $change_state_timeout = $args{change_state_timeout} || $self->{change_state_timeout}; my $timeout = defined( $args{timeout} ) ? $args{timeout} : $change_state_timeout; my $systemshell_password = defined( $args{prompt_password} ) ? $args{prompt_password} : $self->{prompt_password}; my $from = $args{from} || undef; my $regex; my $exception; my $clus_show_details; my %user_defined_states = @{$args{user_defined_states}} if defined $args{user_defined_states}; my %bootstate_opts = ( timeout => $timeout, maxidle => $args{maxidle}, transient => 1, user_defined_states => $args{user_defined_states}, fake_prompt_retry_timeout => $args{fake_prompt_retry_timeout}, ); if (exists $args{append_output_to}) { $bootstate_opts{append_output_to} = $args{append_output_to}; } if ( !$from ) { # find the current state by passing arguments specific to # boot_state_delay ( $from, $regex, $exception ) = $self->boot_state_delay( %bootstate_opts ); } my @msgs; my $tried_direct_login_to_systemshell = 0; my $maxcycle = defined $args{maxcycle} ? $args{maxcycle} : $self->{maxcycle}; my $user_defined_state_msg; my $cycle = ($maxcycle == -1) ? 1 : $maxcycle; my $final_state = $to; local $SIG{ALRM} = sub { NACL::Transit::Exceptions::TransitException->throw("Timed out ($timeout seconds) reaching to State: $final_state"); }; alarm $timeout; while ($cycle) { $cycle-- if ($maxcycle != -1); if ((not defined $user_defined_states{$regex}) and "NACL::Transit::StateDefinitions::$from"->isa("NACL::Transit::StateDefinitions::$to")) { alarm 0; $Log->exit() if $may_exit; return $from; } if ( !@msgs ) { push ( @msgs, $self->next_message(%args, from => $from, to => $to, regex => $regex) ); } if ( !@msgs ) { $Log->exit() if $may_exit; NACL::Transit::Exceptions::Internal->throw( "Internal Error: No action returned"); } if ( ($from eq 'PASSWORD' || $from eq 'MAINT') && $to eq 'MAINT_SYSTEMSHELL' && $regex eq "diag\@localhost\'s password:\\s?" ){ #as per burt 963445 MAINT_SYSTEMSHELL directly prompts for password splice(@msgs,0,2); } my $can_change_ontap = shift @msgs; my $msg = shift @msgs; # GRUB/Rolex hack: GRUB appears to not like taking # command input too quickly (I notice it getting only the # first 16 characters of a command, and it is probably not # a coincidence that serial port hardware decended from # the 16550 contains 16 character wide buffers.) $msg = "boot_ontap maint" if($from =~ /FIRMWARE/ && $to =~ /MAINT/ && $msg =~ /boot_ontap/ ) ; require NACL::C::Node; my $ci = NACL::C::Node->new(hostrec => $conn->hostp()); if($ci->is_cmode() && $from =~ /FIRMWARE/ && $to =~ /MAINT/ && $msg =~ /^boot\cM$/) { $conn->execute(command => "setenv BOOTCMD_ARGS maint"); } # vsa type filers don't accept -skip-lif-migration-before-shutdown option. # pre-cluster nodes don't accept -skip-lif-migration-before-shutdown # option(refer burt#1038865). if ( $msg =~ /system node halt.*\-skip\-lif\-migration\-before\-shutdown/ ) { my $remove_option; if ( $conn->{hostp}->{hosttype} =~ /vsa/ ) { $remove_option = 1; } elsif (! defined $clus_show_details) { eval { $clus_show_details = $conn->execute(command => "clus show" );}; } if ( ! defined $remove_option ) { $clus_show_details = "" if (! defined $clus_show_details); $remove_option = 1 if ( $clus_show_details !~ /\(cluster show\)|Node|Health/i ); } $msg =~ s/ \-skip\-lif\-migration\-before\-shutdown(\s*true)?//g if ($remove_option); } if ( ( $conn->{hostp}->{hosttype} =~ /grub/ ) && ( length($msg) > 16 ) ) { # chop $msg into 16 character max chunks my (@chunks) = ( $msg =~ /.{1,16}/sg ); foreach my $m (@chunks) { say( $conn, $m, '-nocr', '-blind', '-ok' ); sleep(2); } } else { # the non-hack case... say( $conn, $msg, '-nocr', '-blind', '-ok' ); # This is fix for burt568540. # When sudo halt is issue on systemshell prompt filer/vsim takes some # to proceed with the command. Transit think that nothing happened and # will try the same command again and again till retry exhausted. Now # it will sleep for 10 second before retrying again. if ($from eq "SYSTEMSHELL" and $msg =~ /sudo\s+halt/) { sleep(10); } # This is fix for burt1039987. # When halt is issued with -ignore-quorum-warnings # and -skip-lif-migration-before-shutdown options # filer/vsim takes some time to proceed with the command. # Transit think that nothing happened and will try the same command # again and again till retry exhausted. Now it will sleep for 10 second before retrying again. if ( $from eq "CLI" && $msg =~ /system node halt/ && $msg =~ /ignore-quorum-warnings/ && $msg =~ /skip-lif-migration-before-shutdown/ ) { sleep(10); } } my $previous = 'NACL::Transit::StateDefinitions::'.$from; my $probe_timeout = [120]; # Hack for exiting out of Service processor states. The exit sequence # requires an "exit\r" followed by another carriage return (which we're # handling through probe_timeout) $probe_timeout = [1, 2, 4, 8, 120] if $previous->isa('NACL::Transit::StateDefinitions::SERVICE_PROCESSOR'); ( $from, $regex, $exception ) = $self->boot_state_delay(%bootstate_opts, probe_timeout => $probe_timeout ); if ($previous eq "NACL::Transit::StateDefinitions::PASSWORD" && $from eq "USERNAME" && $to eq "SYSTEMSHELL" && $tried_direct_login_to_systemshell != 1) { push (@msgs, 0, "diag", 0, $systemshell_password); $tried_direct_login_to_systemshell = 1; } # Check if Transit reached to some user defined state. Populate @msgs # with answers passed by user to answer filer prompt if ($user_defined_states{$regex}) { # Remove all previously stored messages @msgs = (); # We are not sure if $can_change_ontap should be 0 or 1 # for user_defined_states. Making it 1 (Will skip $can_ontap_change condition) push (@msgs, 1, $user_defined_states{$regex} . "\r"); $can_change_ontap = 1 } $from = 'NACL::Transit::StateDefinitions::'.$from; if ( !$can_change_ontap ) { if ( $previous->is_ONTAP() && !($from->is_ONTAP()) ) { alarm 0; $Log->exit() if $may_exit; NACL::Transit::Exceptions::TransitException->throw( "Reached NONONTAP state '$from' from ONTAP state '$previous' via $msg" ); } if ( !($previous->is_ONTAP()) && $from->is_ONTAP() ) { alarm 0; $Log->exit() if $may_exit; NACL::Transit::Exceptions::TransitException->throw( "Reached ONTAP state '$from' from NONONTAP state '$previous' via $msg" ); } } $previous =~ s/NACL::Transit::StateDefinitions:://; $from =~ s/NACL::Transit::StateDefinitions:://; } alarm 0; $Log->exit() if $may_exit; #In order to avoid "cycled through too many boot states without reaching the right one" issue, #Please refer either of below approaches: # 1: Override max_cycle (default 20) with comparatively bigger value on testcase level. # 2: Verify hosttype value in respective tharnhost (should be as per filer type). my $errormsg = "cycled through too many boot states without reaching the right one ($maxcycle times)"; if (defined $exception) { $errormsg .= " with $exception"; } NACL::Transit::Exceptions::RetriesExceeded->throw($errormsg); return $from; } 1;