## @summary Internal library used to obtain 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::RetrieveState; use base qw /NACL::Transit::StateDefinitions/; 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 strict; use warnings; use Tie::IxHash (); use vars qw($exception); # Exceptions use NACL::Transit::Exceptions::Timeout qw/:try/; use NACL::Transit::Exceptions::Internal qw/:try/; use NACL::Transit::Exceptions::UnexpectedEOF qw/:try/; use NACL::Transit::Exceptions::TransitException qw/:try/; # A helper routine to perform most of "boot_state_delay". # _boot_state_once # determine current state from console output # Listen to the appliance $conn's console to figure what state it is in. # (By default, and depending on the probe_timeout option, # we might send carriage returns to encourage # the console to divulge a prompt). # # On success, return the state and the regex regex that was used to match that # state. On failure, throw the appropriate exception. sub _boot_state_once { my ($self, %opts) = @_; $Log->enter() if $may_enter; my $conn = $self->{conn}; my @probe_timeout = defined( $opts{probe_timeout} ) ? @{ $opts{probe_timeout} } : @{ $self->{probe_timeout} }; # passing append_output_to as undef means we have to consider undef # over the object's value my $append_output_to = exists ( $opts{append_output_to} ) ? $opts{append_output_to} : $self->{append_output_to}; $self->make_prompt_hash() if !$self->{prompt_hash}; my %prompthash; my $prompt_ixhash = tie(%prompthash,"Tie::IxHash",%{$self->{prompt_hash}}); %prompthash = (%prompthash, %{$self->{transient_prompt_hash}}) if $opts{transient}; # Populate %prompthash with user passed states my %user_defined_states = @{$opts{user_defined_states}} if defined $opts{user_defined_states}; %prompthash = (%prompthash, %user_defined_states); my @prompts = keys(%prompthash); my $timeout_scaling_factor = $self->{timeout_scaling_factor}; my $get_state_timeout = $opts{get_state_timeout} || $self->{get_state_timeout}; my $regular_timeout = $opts{timeout} || $get_state_timeout; $regular_timeout = $regular_timeout * $timeout_scaling_factor; $regular_timeout = Tharn::timeout2time( $regular_timeout ); AGAIN: { my ($timeout, $do_probe_timeout); if ( @probe_timeout && Tharn::timeout2time( $probe_timeout[0] ) < $regular_timeout ) { $timeout = $probe_timeout[0]; $do_probe_timeout = 1; } else { $timeout = $regular_timeout; $do_probe_timeout = 0; } my $maxidle = defined $opts{maxidle} ? $opts{maxidle} : $self->{maxidle}; my $timed_out = 0; my $idled = 0; my $at_eof = 0; my $before = ""; my $matching_regex; # burt 328123: When upgrading from ontap 7.x to 8.0, something # like this may be printed, where the ">" at start of line # would otherwise be confused with a NORC prompt: # [...] # CPU Type: Dual Core AMD Opteron(tm) Processor 885 # > setenv AUTOBOOT_FROM primary # > setenv USE_SECONDARY false # [...] # Thus, we ignore it with '-ignore'. The ignore string is # anchored to 'CPU Type' rather than to 'setenv' because it # must ignore even when all we have read so far is the first # ">". my $match = $conn->expect( match => \@prompts, timeout => $timeout, max_idle => $maxidle, ignore => ["^CPU Type: .*(?:\n>.*)+"], on_timeout => sub { $timed_out = 1; undef }, on_idle => sub { $idled = 1; undef }, on_eof => sub { $at_eof = 1; undef }, matching_regex => \$matching_regex, before => \$before, ); if ( $timed_out && $do_probe_timeout ) { $conn->send("\r"); shift @probe_timeout if ( @probe_timeout > 1 ); if ( $opts{_probed} ) { ${ $opts{_probed} } = 1; } redo AGAIN; } elsif ( $timed_out || $idled ) { my $value = $timed_out ? "timeout = $timeout" : "maxidle = $maxidle"; $Log->exit() if $may_exit; NACL::Transit::Exceptions::Timeout->throw( "Timed out waiting for recognizable output to identify current boot state, $value, Connection = '" . $conn->{id} . "' Looking for one of: @prompts" ); } elsif ($at_eof) { if($append_output_to) { $$append_output_to .= $before; } $Log->exit() if $may_exit; NACL::Transit::Exceptions::UnexpectedEOF->throw( "Unexpected EOF while reading console to identify current boot state, Connection = '" . $conn->{id} . "'" ); } elsif ($matching_regex) { if($append_output_to) { $$append_output_to .= $before . $match; } my $state = $prompthash{$matching_regex}; if (defined $user_defined_states{$matching_regex} and $user_defined_states{$matching_regex} eq $state) { $Log->exit() if $may_exit; return ( $state, $matching_regex ); } if($state eq 'CLI' && $match !~ /::/ ) { # now its not in ngsh - could be systemshell or 7Mode if ($match =~ /(__prompt__>|%|#)\s$/) { $state = 'SYSTEMSHELL'; my $conn = $self->{conn}; my $output; eval { $output = $conn->execute( command => "kenv bootarg.init.boot_mode", timeout => 5 ); }; if ($@ =~ /Timeout waiting for command to complete/) { # Refer burt 836795 # At times when the system is halting, due to poking # the filer at this stage, 'kenv bootarg...' is issued again and # causes a timeout (because the system would have started # halting). Hence we try to redo again and reach the # final state. redo AGAIN; } if ($output =~ /maintenance/) { $state = 'MAINT_SYSTEMSHELL'; } } elsif ($conn->{hostp}->hosttype() =~ /-ng/ && $match =~ />\s$/) { $state = 'DBLADECLI'; } } ## Following change is w.r.t. burt957501 if (defined $match && defined $state) { if ($before =~ /(.*)(Error:.*)/ && $state eq 'CLI') { $exception = $2; } } $Log->exit() if $may_exit; NACL::Transit::Exceptions::Internal->throw( "$state is an unknown boot state - package probably not " . "defined" ) unless ( "NACL::Transit::StateDefinitions::$state"->isa( 'NACL::Transit::StateDefinitions::STATE') ); return ( $state, $matching_regex ); } else { $Log->exit() if $may_exit; NACL::Transit::Exceptions::Internal->throw( "Internal error in boot_state_once, timed_out=$timed_out idled=$idled at_eof=$at_eof matching_regex=$matching_regex match=$match" ); } } } # boot_state_delay: # As boot_state_once, but also deal with possible false matches, as # follows. # If we think we're in one of the states listed in this table, we # should listen a bit longer and see if the state changes. If the # state doesn't change within that time, though, we return the state # we initially thought we saw. # For UP, a false prompt may appear as a side effect of how ttymux # emits the last console line again whenever a console/telnet user # logs in. # For any non-transient state, it might be a false hit if we # previously speculatively sent ctrl-C as input. # We need to catch the internal Timeout exception as this is what is thrown # when no output was obtained. We can then decide to rethrow a Transit exception # based on whether or not a state was discovered previously or not. my %MIGHT_BE_FALSE = ( BOOT_MENU => 2, CLI => 5, ); sub boot_state_delay { my $self = shift; $Log->enter() if $may_enter; my %opts = @_; my ( $state, $newstate, $regex, $newregex ); my $_probed_scalar; $opts{_probed} = \$_probed_scalar; ( $state, $regex ) = $self->_boot_state_once(%opts); my %user_defined_states = @{$opts{user_defined_states}} if defined $opts{user_defined_states}; my $fake_prompt_retry_timeout = $opts{fake_prompt_retry_timeout} || $self->{fake_prompt_retry_timeout}; my $is_transient; while (1) { last if !$state; my $retry = $MIGHT_BE_FALSE{$state}; # User defined state shouldnt be transient states if (defined $user_defined_states{$regex} and $user_defined_states{$regex} eq $state) { $is_transient = 0; } else { $is_transient = "NACL::Transit::StateDefinitions::$state"->is_Transient(); } if ( !defined($retry) && !$is_transient && ${ $opts{_probed} } ) { # We have recently speculatively sent a carraige return to # try to scare up a prompt, but this may have caused more # prompts i.e. the prompt we're looking at now might not # be the latest. $self->{conn}->discardbuff(timeout => 0.5); } last if !$retry; ( $newstate, $newregex ) = (undef, undef); try { # $retry is passed as timeout here and time_scaling_factor # should not be applicable on $retry time. Dividing $retry # by $timeout_scaling_factor to nullify the effect of multiplication at later stage $retry = $retry / $self->{timeout_scaling_factor}; ( $newstate, $newregex ) = $self->_boot_state_once( %opts, timeout => $retry, probe_timeout => [] ); } catch NACL::Transit::Exceptions::Timeout with { }; last if !$newstate; ( $state, $regex ) = ( $newstate, $newregex ); } # User defined state shouldnt be transient states if (defined $user_defined_states{$regex} and $user_defined_states{$regex} eq $state) { $is_transient = 0; } else { $is_transient = "NACL::Transit::StateDefinitions::$state"->is_Transient(); } $Log->exit() if $may_exit; alarm 0 if !$state; NACL::Transit::Exceptions::TransitException->throw( "Could not match any state" ) if !$state; return ( $state, $regex, $exception ); } 1;