## Copyright (c) 2017 NetApp, Inc., All Rights Reserved ## Any use, modification, or distribution is prohibited ## without prior written consent from NetApp, Inc. ## ## @summary Automated NDU Framework ## @author lkashyap@netapp.com ## chakrava@netapp.com ## @status review ## @pod here package NACL::MTask::ClusterImage; use strict; use warnings; use Hostrec qw(); use Params::Validate qw(validate validate_with BOOLEAN SCALAR HASHREF OBJECT SCALARREF ARRAYREF); use NATE::Exceptions::Argument qw(:try); use NATE::BaseException; use NATE::Log qw(log_global); use NACL::APISet::Exceptions::TimeoutException (); use NACL::Exceptions::InvalidChoice; use NACL::ComponentUtils qw(Dumper); use NACL::C::Cserver; use NACL::C::ClusterHa; use NACL::STask::ClusterImage; use NACL::STask::Node; use base qw (NACL::MTask::MTask); =head1 NAME NACL::MTask::ClusterImage =head1 SYNOPSIS Example: 1: Update without pause after each HOP my %constructor_params; $constructor_params{command_interface} = $Nodes[0]; ## Params require for Update $constructor_params{target_version} = $Target_Version; $constructor_params{pause_after_update}= $Pause_After_Update; $constructor_params{is_lts_upgrade} = $Is_Lts_Upgrade; $constructor_params{upgrade_path} = $Upgrade_Path; $Upgrade_Obj = NACL::MTask::ClusterImage->new(%constructor_params); $Upgrade_Obj->update(); (Instance Method) Assume you are updating LB.1 to FT with Non-Lts path and pause_after_update = 1 The upgrade path will be LB.1->HN->FT. 2: Resume_Update After each upgrade it will come out of the loop and gives handler to user, to perform his task. In your thpl call, to continue the upgrade loop on the remain versions, that needed to be upgrade. To get the remaining versions, call the following param from the object that created above. my $remaining_upgrade_versions = @{ $Upgrade_Obj->{version_strings} }; for ( 1 .. $remaining_upgrade_versions) { $Log->step('.thpl call: Resume update'); $Upgrade_Obj->resume_update(); } 3: Update withOUT pause after each HOP .. $Pause_After_Update = 0; $constructor_params{pause_after_update} = $Pause_After_Update; .. $Upgrade_obj_without_pause = NACL::MTask::ClusterImage->new(%constructor_params); $Upgrade_obj_without_pause->update(); Note: Here you are not pausing the upgrade, Resume will not work. 4: Mixed_version_update with pause after each HOP If you want mixed version update construct an object with following params $constructor_params{target_version} = $Target_Version; $constructor_params{pause_after_update}= $Pause_After_Update; $constructor_params{is_lts_upgrade} = $Is_Lts_Upgrade; $constructor_params{upgrade_path} = $Upgrade_Path; $constructor_params{mixed_version} = $Mixed_version; $constructor_params{mixed_version_nodes} = $Mixed_Version_Nodes; $Mixed_version_upgrade_obj = NACL::MTask::ClusterImage->new(%constructor_params); $Mixed_version_upgrade_obj->update(); (Instance Method) 5: Resume_Mixed_Version_Update After each upgrade it will come out of the loop and gives handler to user, to perform his task. In your thpl call, to continue the upgrade loop on the remain versions, that needed to be upgrade. To get the remaining versions, call the following param from the object that created above. my $remaining_upgrade_versions = @{ $Mixed_version_upgrade_obj->{mixed_version_strings} }; for ( 1 .. $remaining_upgrade_versions) { $Log->step('.thpl call: Resume update'); $Mixed_version_upgrade_obj->resume_update(); } 6: Mixed_version_update withOUT pause after each HOP .. $Pause_After_Update = 0; $constructor_params{pause_after_update} = $Pause_After_Update; .. $Mixed_version_upgrade_obj_without_pause = NACL::MTask::ClusterImage->new(%constructor_params); $Mixed_version_upgrade_obj_without_pause->update(); Note: Here you are not pausing the upgrade, Resume will not work. 7: Revert with pause after each version revert ## Params required for Revert my %constructor_params; $constructor_params{command_interface} = $Nodes[0]; ($Pause_After_Revert = 1) $constructor_params{revert_to} = $Revert_to; $constructor_params{pause_after_revert}= $Pause_After_Revert; $Revert_Obj = NACL::MTask::ClusterImage->new(%constructor_params); $Revert_Obj->update(); (Instance Method) 8: Resume_Revert After each revrt it will come out of the loop and gives handler to user, to perform his task. In your thpl call, to continue the revert, loop on the remain versions, that needed to be revert. To get the remaining versions, call the following param from the object that created above. my $remaining_revert_versions = @{ $Revert_Obj->{revert_version_strings} }; for ( 1 .. $remaining_revert_versions) { $Log->step('.thpl call: Resume revert'); $Revert_Obj->resume_revert(); } 9: Revert withOUT pause after each HOP .. $Pause_After_Revert = 0; $constructor_params{pause_after_revert} = $Pause_After_Revert; .. $Revert_obj_without_pause = NACL::MTask::ClusterImage->new(%constructor_params); $Revert_obj_without_pause->revert(); Note: Here you are not pausing the upgrade, Resume will not work. =head1 DESCRIPTION This taks performs the update/upgrade of clusters using the automated nondisruptive update (ANDU) feature. C is a derived class of L. It is a task to perform an ONTAP ClusterImage Update/Upgrade given either a URL, a package, or the relevant parts of the path that would be used by this tals to derive a package e.g the ONTAP directory path, build flags and architecture name =head1 ATTRIBUTES =over =item C<< command_interface => $command_interface >> =back =head1 METHODS =head2 update $Update_Obj->update( command_interface => $Nodes[0], 'stabilize-minutes' => $Stabilize_mins, ); or $Update_Obj->update( command_interface => $Nodes[0], 'stabilize-minutes' => $Stabilize_mins, arch => 'x86_64', build_flags => "debug domain sim" ontap_dir => $ontap_dir, ); All the nodes in the cluster will be updated/upgraded and this task will refresh the command_interface following node reboot as part of the upgrade process. =cut =over =item Options =over =item C<< command_interface => $ci >> (Required) See L =item C<< build_flags => \@ >> (Optional) The build flags, e.g. pbo, debug, sim, gcov, etc., if "url" is not provided. =item C<< arch => $architecture >> (Optional) The architecture type, e.g. pc_elf, x86-64 etc., if "url" is not provided. =item C<< web_host => xyz >> (Optional) The hostname of the server to run an http_server on, defaults to "localhost" =item C<< stabilize-minutes => 2 >> (Optional) Specifies the number of minutes that the update should wait after a takeover or giveback is completed. This allows time for the clients to recover from the pause in I/O that occurs during takeover and give-back. =item C<< 'method-timeout' => $timeout_value_in_seconds >> (Optional) Default to 3600 seconds How long to wait for the completion of job started by 'update' operation. =item C<< 'force-rolling' => false|true >> (Optional) Default to false i.e. performs batch upgrade This option is used for clusters with eight or more nodes to specify that a rolling update (one HA pair at a time) should be done. =item C<< 'wait_for_completion' => 1|0 >> (Optional) Defaults to 1 i.e the update method does not return after starting the update. The update progress will be monitored for completion. Used for QA testing If set to 0, the update method starts the update and returns immediately, it does not monitor the update after starting it. =item C<< 'mcc' => 1|0 >> (Optional) Defaults to 0 i.e ONTAP update is on HA pair If set to 1, the update is on 4/8 pack MCC =item C< This type of exception is thrown when essential parameters are not defined or has null value =back =back =head2 revert $Upgrade_Obj->revert(); or $Upgrade_Obj->revert( command_interface => $Nodes[0], revert_to => 'beer__x_x_x', ); All the nodes in the cluster will be revert and this task will refresh the command_interface following node reboot as part of the revert process. =over =item Options =over =item C<< command_interface => $ci >> (Required) See L (Required if ontap_dir, build_flags and arch are not provided) =item C<< 'revert_to' => $revert_to_version >> (Required) The Nodes will be reverted to specified version, eg: 'Fattire__9_3_0' =back =back =cut use Class::MethodMaker [ scalar => 'volume', scalar => 'target_version', scalar => 'is_lts_upgrade', scalar => 'validated_target_version', scalar => 'command_interface', scalar => 'Cserver', scalar => 'upgrade_versions', scalar => 'nodes_ref', scalar => 'node_version', scalar => 'source_version', scalar => 'source_version_change_number', scalar => 'hardware_model', scalar => 'list_of_upgrade_versions', scalar => 'version_strings', scalar => 'web_host', scalar => 'ontap_dir', scalar => 'build_flags', scalar => 'method_timeout', scalar => 'force_rolling', scalar => 'stabilize_minutes', scalar => 'mcc', scalar => 'arch', scalar => 'mixed_version_nodes', scalar => 'package_url', scalar => 'pause_after_update', scalar => 'pause_after_revert', scalar => 'mixed_version', scalar => 'mixed_version_strings', scalar => 'revert_to_version_strings', scalar => 'revert_versions', scalar => 'revert_to', scalar => 'total_nodes', ]; my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); my %Default_Andu_Options = ( 'mcc' => 0, 'build_flags' => 'debug', 'stabilize_minutes' => 8, 'wait_for_completion' => 1, 'pause_after_update' => 0, 'pause_after_revert' => 0, 'arch' => 'x86_64', 'force-rolling' => 'false', 'method_timeout' => 7200, ); my $upgrade_version_ref = { 'Fullsteam__8_3_0' => { index => 1.5, upgrades_to => [ 'Fullsteam__8_3_1', 'Fullsteam__8_3_2', 'Longboard__9_0_0', 'Longboard__9_1_0', 'Harpoon__9_2_0', 'Fattire__9_3_0', 'devN', ], build_path => "", platform_validation_path => "", alias => 'R8.3.0xN', version_string => 'Fullsteam__8.3.0', is_lts => '0', revert_index => '1', }, 'Fullsteam__8_3_1' => { index => 1.5, upgrades_to => [ 'Fullsteam__8_3_2', 'Longboard__9_0_0', 'Longboard__9_1_0', 'Harpoon__9_2_0', 'Fattire__9_3_0', 'devN', ], build_path => "/x/eng/rlse/DOT/R8.3.1xN/final/bedrock/export", platform_validation_path => "", alias => 'R8.3.1xN', version_string => 'Fullsteam__8.3.1', is_lts => '0', revert_index => '2', }, 'Fullsteam__8_3_2' => { index => 1.5, upgrades_to => [ 'Longboard__9_0_0', 'Longboard__9_1_0', 'Harpoon__9_2_0', 'Fattire__9_3_0', 'devN', ], build_path => "/x/eng/rlse/DOT/R8.3.2xN/final/bedrock/export", platform_validation_path => "", alias => 'R8.3.2xN', version_string => 'Fullsteam__8.3.2', is_lts => '0', revert_index => '3', }, 'Longboard__9_0_0' => { index => 2, alias => 'R9.0xN|R8.4xN', version_string => 'Longboard__9.0.0', is_lts => '0', revert_index => '4', upgrades_to => [ 'Longboard__9_1_0', 'Harpoon__9_2_0', 'Fattire__9_3_0', 'devN', ], build_path => "/x/eng/rlse/DOT/R9.0xN/final/bedrock/export", platform_validation_path => "/x/eng/rlse/DOT/R9.0xN/ndu/upgrademgr/src/validations_scripts/validation_rules_platform_check.php", }, 'Longboard__9_1_0' => { index => '2.5', upgrades_to => [ 'Harpoon__9_2_0', 'Fattire__9_3_0', 'devN', ], alias => 'R9.1xN|R8.4.1xN|R8.4.1RC\dxN', version_string => 'Longboard__9.1.0', is_lts => '1', revert_index => '5', build_path => "/x/eng/rlse/DOT/R9.1xN/final/bedrock/export", platform_validation_path => "/x/eng/rlse/DOT/R9.1xN/ndu/upgrademgr/src/validations_scripts/validation_rules_platform_check.php", }, 'Harpoon__9_2_0' => { index => '3.1', upgrades_to => [ 'Fattire__9_3_0', 'devN', ], alias => 'R9.2xN', version_string => 'Harpoon__9.2.0', is_lts => '0', revert_index => '6', build_path => "/x/eng/rlse/DOT/R9.2xN/final/bedrock/export", platform_validation_path => "/x/eng/rlse/DOT/R9.2xN/ndu/upgrademgr/src/validations_scripts/validation_rules_platform_check.php", }, 'Fattire__9_3_0' => { index => 4, upgrades_to => ['devN'], alias => 'R9.3xN', is_lts => '1', revert_index => '7', version_string => 'Fattire__9.3.0', build_path => "/x/eng/rlse/DOT/R9.3xN/final/bedrock/export", platform_validation_path => "/x/eng/rlse/DOT/devN/ndu/upgrademgr/src/validations_scripts/validation_rules_platform_check.php", }, 'devN' => { index => 5, upgrades_to => [], alias => 'devN', version_string => 'devN', is_lts => '0', revert_index => '8', build_path => "/x/eng/rlse/DOT/devN/final/bedrock/export", platform_validation_path => "/x/eng/rlse/DOT/devN/ndu/upgrademgr/src/validations_scripts/validation_rules_platform_check.php", }, }; sub new { $Log->enter() if $may_enter; my $pkg = shift; my $self; my %opts; %opts = ( %Default_Andu_Options, %opts ); %opts = validate_with( params => \@_, spec => { url => { type => SCALAR, optional => 1, }, arch => { type => SCALAR, optional => 1, default => 'x86_64', }, nodes => { type => SCALAR, optional => 1, }, client => { type => SCALAR, optional => 1, default => 'localhost' }, ontap_dir => { type => SCALAR, optional => 1, default => undef }, build_flags => { type => SCALAR, optional => 1, default => 'debug' }, pause_after_update => { type => BOOLEAN, optional => 1, default => 0 }, 'method_timeout' => { type => SCALAR, optional => 1, default => 7200, }, 'force-rolling' => { type => SCALAR, optional => 1, }, 'stabilize-minutes' => { type => SCALAR, optional => 1, }, mcc => { type => BOOLEAN, optional => 1, default => 0, }, }, allow_extra => 1, ); $pkg->upgrade_versions($upgrade_version_ref); $self->{upgrade_versions} = $pkg->upgrade_versions; if ( defined $opts{upgrade_path} ) { chomp $opts{upgrade_path}; my @list_of_upgrade_versions = split /,/, $opts{upgrade_path}; my $validation_flag = 0; # Validate the versions entered by user foreach my $var (@list_of_upgrade_versions) { chomp $var; foreach my $ele ( keys %{ $pkg->upgrade_versions } ) { if ( $var ne $ele ) { next; } else { $validation_flag++; } } } if ( $validation_flag !~ @list_of_upgrade_versions ) { $Log->exit() if $may_exit; NATE::BaseException->throw( 'The list of upgrade versions given are not in valid format'); } $self->{list_of_upgrade_versions} = \@list_of_upgrade_versions; } $pkg->web_host( Hostrec->new( id => 'localhost' ) ); $self->{web_host} = $pkg->web_host; foreach ( keys(%opts) ) { $self->{$_} = $opts{$_}; } my @nodes = NACL::STask::Node->find(); $self->{nodes_ref} = $pkg->nodes_ref( \@nodes ); $self->{Cserver} = NACL::C::Cserver->find( command_interface => $opts{command_interface} ); my @total_nodes; foreach (@nodes) { push @total_nodes, ${_}->name(); } $self->{total_nodes} = \@total_nodes; bless $self, $pkg; return $self; $Log->exit() if $may_exit; } sub update { $Log->enter() if $may_enter; my $self = shift; my %opts = @_; foreach ( keys(%opts) ) { $self->{$_} = $opts{$_}; } my $failure_flag = 0; $Log->step('Get Node Version'); $self->_get_node_version(); $Log->comment( 'Node version is: ' . $self->{node_version} ); $Log->comment( 'The source change number: ' . $self->{source_version_change_number} ); $Log->step('Validate Source Version'); $self->_validate_source_version( $self->{node_version} ); $Log->comment( 'The source version is: ' . $self->{source_version} ); $Log->step('Validate Target Version'); $Log->comment( 'init target version: ' . $self->{target_version} ); $self->_target_version_validation(); $Log->comment( 'The validated target version is: ' . $self->{validated_target_version} ); $Log->step('Check for upgrade possibility'); $self->_check_for_upgrade_possibility(); $Log->step('Is hardware compatable? Check'); $self->_is_hardware_compatable(); $Log->comment( 'The hardware is: ' . $self->{hardware_model} ); # Get the version strings for update $Log->step('Validate the upgrade path'); my @version_strings; if ( $self->source_version !~ /$self->validated_target_version/ ) { $Log->comment( 'source version NOT equalto target version so use _suggest_upgrade_path' ); # Get the upgrade possible upgrade paths my $upgrade_path = $self->_suggest_upgrade_path(); # Split the Upgrade Path to know the list of builds Upgrading. @version_strings = @$upgrade_path; # Splice first build, which is source version. splice( @version_strings, 0, 1 ); } else { $Log->comment('Source version is equals to Target Version'); # Push the source version to the version_strings array. @version_strings = ( $self->source_version ); } $self->{version_strings} = \@version_strings; my @revert_to_version_strings = @version_strings; $self->{revert_to_version_strings} = \@revert_to_version_strings; $Log->step('Update Build Path'); $self->_update_build_path(); my @mixed_version_strings; if ( $self->mixed_version == 1 ) { # get the subset nodes my $mixed_version_nodes_setA = $self->mixed_version_nodes; my @setA_nodes = split /,/, $mixed_version_nodes_setA; my ( @setB_nodes, $mixed_version_nodes_setB, %has ); NATE::BaseException->throw('Specify required nodes') if @setA_nodes < 2; my @get_partner_node; foreach my $node_obj ( @{ $self->{nodes_ref} } ) { my $partner_name = $node_obj->get_partner() if ( $node_obj->name() eq $setA_nodes[0] ); @get_partner_node = grep { $_ eq $partner_name } @setA_nodes; last if $get_partner_node[0]; } NATE::BaseException->throw('Specify correct HA pair nodes') if ( !@get_partner_node[0] ); if ( @{ $self->total_nodes } > 2 ) { foreach my $setA_node (@setA_nodes) { foreach my $node ( @{ $self->total_nodes } ) { $has{$node}++ if ( $node ne $setA_node ); } } foreach my $v ( keys %has ) { push @setB_nodes, $v if ( $has{$v} == '2' ); } $mixed_version_nodes_setB = join( ',', @setB_nodes ); } elsif ( @{ $self->total_nodes } == 2 ) { NATE::BaseException->throw( 'ANDU doesnt support 2 Node mixed version'); } # Get the Duplicates of the version which require mixed version. for ( my $i = 0 ; $i < @{ $self->{version_strings} } ; $i++ ) { my $dup_ele = $self->{version_strings}->[$i]; push @{ $self->{mixed_version_strings} }, $dup_ele; push @{ $self->{mixed_version_strings} }, $self->{version_strings}->[$i]; } $self->{version_strings} = (); # Start the update $Log->step('Start MIXED VARSION UPDATE'); my $loop_count = @{ $self->{mixed_version_strings} }; for ( my $i = 0 ; $i < $loop_count ; $i++ ) { $self->{ontap_dir} = $self->upgrade_versions->{ $self->{mixed_version_strings}->[0] } ->{build_path}; my $proc = NATE::Process->new( codespec => \&_andu, args => [$self], runid => "update to version $self->{mixed_version_strings}->[0]", onexit => \&NATE::Process::on_exit_die_worst_result, inherit_params => 1, ); $proc->start(); $proc->wait(); $proc->destroy(); if ( $self->pause_after_update == 1 ) { splice( @{ $self->{mixed_version_strings} }, 0, 1 ); return $TCD::PASS; } splice( @{ $self->{mixed_version_strings} }, 0, 1 ); } } else { # Start the update $Log->step('Start UPDATE'); my $loop_count = @{ $self->{version_strings} }; for ( my $i = 0 ; $i < $loop_count ; $i++ ) { $Log->comment( 'Updating to version: ' . $self->{version_strings}[0] ); $self->{ontap_dir} = $self->upgrade_versions->{ $self->{version_strings}->[0] } ->{build_path}; my $proc = NATE::Process->new( codespec => \&_andu, args => [$self], runid => "update to version $self->{version_strings}->[0]", onexit => \&NATE::Process::on_exit_die_worst_result, inherit_params => 1, ); $proc->start(); $proc->wait(); $proc->destroy(); if ( $self->{pause_after_update} == 1 ) { splice( @{ $self->{version_strings} }, 0, 1 ); return $TCD::PASS; } splice( @{ $self->{version_strings} }, 0, 1 ); } } $Log->exit() if $may_exit; } sub resume_update { $Log->enter() if $may_enter; my $self = shift; my %opts = @_; foreach ( keys(%opts) ) { $self->{$_} = $opts{$_}; } NATE::BaseException->throw('There are NO pending upgrades to perform') if ( ( !$self->{version_strings} ) || ( @{ $self->{version_strings} } == 0 ) ); my $loop_count = @{ $self->{version_strings} }; for ( my $res = 0 ; $res < $loop_count ; $res++ ) { $Log->comment( 'Updating to version: ' . $self->{version_strings}[0] ); $self->{ontap_dir} = $self->upgrade_versions->{ $self->{version_strings}->[0] } ->{build_path}; my $proc = NATE::Process->new( codespec => \&_andu, args => [$self], runid => "update to version $self->{version_strings}->[0]", onexit => \&NATE::Process::on_exit_die_worst_result, inherit_params => 1, ); $proc->start(); $proc->wait(); $proc->destroy(); if ( $self->pause_after_update == 1 ) { splice( @{ $self->{version_strings} }, 0, 1 ); return $TCD::PASS; } splice( @{ $self->{version_strings} }, 0, 1 ); } $Log->exit() if $may_exit; } sub resume_mixed_version_update { $Log->enter() if $may_enter; my $self = shift; my %opts = @_; foreach ( keys(%opts) ) { $self->{$_} = $opts{$_}; } $Log->step('Resume Mixed Version UPDATE'); NATE::BaseException->throw('There are NO pending upgrades to perform') if ( ( @{ $self->{mixed_version_strings} } == 0 ) ); my $loop_count = @{ $self->{mixed_version_strings} }; for ( my $res = 0 ; $res < $loop_count ; $res++ ) { $Log->comment( 'Updating to version: ' . $self->{mixed_version_strings}[0] ); $self->{ontap_dir} = $self->upgrade_versions->{ $self->{mixed_version_strings}->[0] } ->{build_path}; my $proc = NATE::Process->new( codespec => \&_andu, args => [$self], runid => "update to version $self->{mixed_version_strings}->[0]", onexit => \&NATE::Process::on_exit_die_worst_result, inherit_params => 1, ); $proc->start(); $proc->wait(); $proc->destroy(); if ( $self->pause_after_update == 1 ) { splice( @{ $self->{mixed_version_strings} }, 0, 1 ); return $TCD::PASS; } splice( @{ $self->{mixed_version_strings} }, 0, 1 ); } $Log->exit() if $may_exit; } sub revert { $Log->enter() if $may_enter; my $self = shift; my %opts = @_; foreach ( keys(%opts) ) { $self->{$_} = $opts{$_}; } $self->_get_node_version(); my $source_version = $self->_validate_source_version( $self->node_version ); my $SV = $source_version; my $revert_TV = $self->revert_to; my $SV_revert_index = $self->upgrade_versions->{$SV}->{'revert_index'}; my $TV_revert_index = $self->upgrade_versions->{$revert_TV}->{'revert_index'}; my @revert_versions; foreach my $rev_var_index ( ($TV_revert_index) .. ( $SV_revert_index - 1 ) ) { foreach my $ver ( keys %{ $self->upgrade_versions } ) { if ( $self->upgrade_versions->{$ver}->{'revert_index'} == $rev_var_index ) { push @revert_versions, $ver; } } } @{ $self->{revert_versions} } = reverse @revert_versions; foreach my $revert_version ( @{ $self->revert_versions } ) { $Log->comment( 'Performing revert to version: ' . $revert_version ); $self->upgrade_versions->{$revert_version}->{'build_path'} =~ /(\/x\/eng\/rlse\/DOT\/.*)\/final\//; my $buildroot = $1; my $natelib_pathA = $1 . '/test'; my $natelib_pathB = $1 . '/test/lib'; my $revert_opts = { REVERT_BUILDROOT => $buildroot, REVERT_CLUSTER => $self->total_nodes->[0], NATE_LIB => "$natelib_pathB;$natelib_pathA", }; my $proc = NATE::Process->new( codespec => 'STEP/NDU/revert.thpl', params => $revert_opts, runid => "revert_to_$revert_version", onexit => \&NATE::Process::on_exit_die_worst_result, inherit_params => 1, ); $proc->start(); $proc->wait(); $proc->destroy(); if ( $self->pause_after_revert == 1 ) { shift @{ $self->revert_versions }; return $TCD::PASS; } shift @{ $self->revert_versions }; } $Log->exit() if $may_exit; } sub resume_revert { $Log->enter() if $may_enter; my $self = shift; my %opts = @_; foreach ( keys(%opts) ) { $self->{$_} = $opts{$_}; } NATE::BaseException->throw('There are NO pending upgrades to perform') if ( ( !$self->{revert_versions} ) || ( @{ $self->{revert_versions} } == 0 ) ); my $loop_count = @{ $self->{revert_versions} }; for ( my $res = 0 ; $res < $loop_count ; $res++ ) { $Log->comment( 'Reverting to version: ' . $self->{revert_versions}[0] ); $self->upgrade_versions->{ $self->{revert_versions}[0] }->{'build_path'} =~ /(\/x\/eng\/rlse\/DOT\/.*)\/final\//; my $buildroot = $1; my $revert_opts = { REVERT_BUILDROOT => $buildroot, REVERT_CLUSTER => $self->total_nodes->[0], }; my $proc = NATE::Process->new( codespec => 'STEP/NDU/revert.thpl', params => $revert_opts, runid => 'revert_to_' . $self->{revert_versions}[0], onexit => \&NATE::Process::on_exit_die_worst_result, inherit_params => 1, ); $proc->start(); $proc->wait(); $proc->destroy(); if ( $self->pause_after_revert == 1 ) { shift @{ $self->revert_versions }; return $TCD::PASS; } splice( @{ $self->{revert_versions} }, 0, 1 ); } $Log->exit() if $may_exit; } sub _get_cserver { my $self = shift; my %opts = @_; $self->Cserver( NACL::C::Cserver->find( command_interface => $self->command_interface ) ); $Log->comment( 'Cserver is hosted on: ' . $self->{Cserver}->name ); } sub _get_node_version { my $self = shift; # get node apiset object my $node_apiset_obj = @{ $self->{nodes_ref} }[0]->apiset(); my $version_manager = $node_apiset_obj->get_version_manager(); $self->{node_version} = $version_manager->get_version_attribute( attribute => "node_version" ); $self->{source_version_change_number} = $version_manager->get_version_attribute( attribute => "change_number" ); } sub _validate_source_version { my $self = shift; foreach my $version ( keys %{ $self->{upgrade_versions} } ) { chomp($version); next unless ( $self->{node_version} =~ /$self->{upgrade_versions}->{$version}->{alias}/ ); $self->{source_version} = $version; } NATE::BaseException->throw('Cant able to validate source version') if ( !$self->{source_version} ); return $self->{source_version}; } sub _target_version_validation { my $self = shift; if ( $self->{target_version} !~ /\d{6}_\d{4}/ ) { NATE::BaseException->throw('Enter target_version in valid format'); } # Match the version string with the alias of Upgrade_Versions hash and get # the respective key (reverse lookup using value 'alias') foreach my $version ( keys %{ $self->{upgrade_versions} } ) { if ( $self->{target_version} =~ /$self->{upgrade_versions}->{$version}->{alias}/ ) { $self->{validated_target_version} = $version; } last if ( $self->{validated_target_version} ); } NATE::BaseException->throw( 'Provided target version is not available in Upgrade_Versions hash') if ( !$self->{validated_target_version} ); } sub _check_for_upgrade_possibility { my $self = shift; if ( $self->{upgrade_versions}->{ $self->source_version }->{index} > $self->{upgrade_versions}->{ $self->validated_target_version }->{index} ) { NATE::BaseException->throw( 'Cant upgrade as source version is higher than target'); } } sub _is_hardware_compatable { my $self = shift; my $nodescope_apiset_obj = @{ $self->{nodes_ref} }[0]->get_7m_or_nodescope_apiset(); my $sysconfig_reponse = $nodescope_apiset_obj->sysconfig( "verbose" => 1 ); my $hardware_model = @{ $sysconfig_reponse->get_parsed_output() }->[0]->{slot_information} ->[0]->{model_name}; my $version_info = @{ $sysconfig_reponse->get_parsed_output() }->[0]->{version_info}; # validate the platform, whether it suits for upgrade or not. if ( $hardware_model ne 'SIMBOX' ) { my $validation_path = $self->upgrade_versions->{ $self->target_Version } ->{platform_validation_path}; my $platform_check_status = `grep -c $hardware_model $validation_path`; $Log->comment("Platform_Check_Status : $platform_check_status"); if ( $platform_check_status == 0 ) { NATE::BaseException->throw( 'The Platform is not supported for target version: ' . $self->target_version ); } } else { $Log->comment('The platform check is not required, it is Vsim'); } $self->hardware_model($hardware_model); } sub _update_build_path { my $self = shift; $Log->comment('Validate the Build path'); if ( $self->target_version =~ /((R\d.\d.\dRC\dx|R\d.\dRC\dx|R\d\.\d\.\dx|dev|R\d\.\dx)N_\d{6}_\d{4})/ ) { # Check the particular build available or not my $temp_target_version = $self->target_version; my $result = `cat /x/eng/rlse/DOT/$temp_target_version/P4SYNC`; if ( $result =~ /No such file or directory/ ) { NATE::BaseException->throw( 'There is no such build which you entered'); } elsif ( $self->target_version =~ /$self->upgrade_versions{$self->source_version}->{alias}/ ) { # Get the source and target version change numbers my $target_version_change_number = $result; # Check whether the target change > source change if ( $target_version_change_number > $self->source_version_change_number ) { # If (target change > source change), { proceed update } my $build_path_flag = 0; foreach my $version ( @{ $self->version_strings } ) { if ( $self->target_version =~ /$self->{upgrade_versions}{$version}->{alias}/ ) { $self->{upgrade_versions}{$version}->{build_path} = '/x/eng/rlse/DOT/' . $self->target_version . '/final/bedrock/export'; $Log->debug( 'The build path is: ' . $self->{upgrade_versions}{$version} ->{build_path} ); # Set the flag if the build got updated $build_path_flag = 1; } } if ( !$build_path_flag ) { NATE::BaseException->throw( 'The target version which is given is not updated in version\'s build path' ); } } elsif ( $target_version_change_number == $self->source_version_change_number ) { NATE::BaseException->throw( 'The Upgrade is not possible as the source (' . $self->source_version_change_number . ') and target(' . $target_version_change_number . ') version change number is same ' ); } else { NATE::BaseException->throw( 'The Upgrade is not possible as the source change (' . $self->source_version_change_number . ') is is higher than the Target change (' . $target_version_change_number . ')' ); } } else { my $build_path_flag_for_diff_versions; foreach my $version ( @{ $self->version_strings } ) { if ( $self->target_version =~ /$self->{upgrade_versions}{$version}->{alias}/ ) { $self->{upgrade_versions}{$version}->{build_path} = '/x/eng/rlse/DOT/' . $self->target_version . '/final/bedrock/export'; $Log->debug( 'The build path is: ' . $self->{upgrade_versions}{$version}->{build_path} ); # Set the flag if the build is updated $build_path_flag_for_diff_versions = 1; } } if ( !$build_path_flag_for_diff_versions ) { NATE::BaseException->throw( 'The target version which is given is not updated in version\'s build path' ); } } } else { NATE::BaseException->throw( 'validation of the target version is unsuccessful, enter valid build' ); } } sub _suggest_upgrade_path { my $self = shift; my $interactive = 1; my $final_path; # Get the possible upgrade paths my $result_return_upgrade_paths = $self->_return_upgrade_paths( $self->source_version, $self->validated_target_version, $interactive ); if ( $self->is_lts_upgrade == 0 ) { foreach my $path (@$result_return_upgrade_paths) { my @suggested_path = @$path; my @user_path = @{ $self->list_of_upgrade_versions }; if ( @user_path == @suggested_path ) { my $j = 0; for ( my $k = 0 ; $k < @user_path ; $k++ ) { $j++ if ( $user_path[$k] eq $suggested_path[$k] ); } if ( $j == @user_path ) { $final_path = $path; last; } else { NATE::BaseException->throw( 'Please enter the valid and ordered upgrade path'); } } else { next; } } } else { my ( @final_list_ver, @final_lts_path ); foreach my $path (@$result_return_upgrade_paths) { @final_list_ver = @$path; for ( my $i = 0 ; $i < @final_list_ver ; $i++ ) { if ( defined $self->{upgrade_versions} { $final_list_ver[ ${i} + 1 ] } ) { splice( @final_list_ver, $i, 1 ) if ( ( $self->{upgrade_versions} { $final_list_ver[ ${i} - 1 ] }->{'is_lts'} == 1 ) && ( $self->{upgrade_versions} { $final_list_ver[ ${i} + 1 ] }->{'is_lts'} == 1 ) && ( $self->{upgrade_versions}{ $final_list_ver[$i] } ->{'is_lts'} == 0 ) ); } } push @final_lts_path, \@final_list_ver; } foreach my $path (@final_lts_path) { my @suggested_path = @$path; my @user_path = @{ $self->list_of_upgrade_versions }; if ( @user_path == @suggested_path ) { my $j = 0; for ( my $k = 0 ; $k < @user_path ; $k++ ) { $j++ if ( $user_path[$k] eq $suggested_path[$k] ); } if ( $j == @user_path ) { $final_path = $path; last; } else { NATE::BaseException->throw( 'Please enter the valid and ordered upgrade path'); } } else { next; } } } NATE::BaseException->throw( 'Please enter the valid and ordered upgrade path') if ( !$final_path ); return $final_path; } sub _return_upgrade_paths { ## This routine will return the possible upgrade paths, ## Inputs are the source version and target version my $self = shift; my $source_version = shift; my $target_version = shift; my $default = shift; #default is INTERACTIVE var. my %upgrade_versions = %{ $self->upgrade_versions }; ## Here we are getting all the paths that were possible, ## between the source and target versions. ## _findpaths() routine will do that job my $upgrade_paths = $self->_findpaths( {}, $source_version, $target_version ); my @all_positive_routes; my @copy = (); ## From all the possible upgrade paths we are sorting the ## possible paths while ( my $path = shift(@$upgrade_paths) ) { for ( my $x = 0 ; $x < ( scalar(@$path) - 1 ) ; $x++ ) { my $source_index = $upgrade_versions{ $path->[$x] }->{index}; my $target_index = $upgrade_versions{ $path->[ $x + 1 ] }->{index}; if ( ( $target_index - $source_index ) <= 1 ) { splice( @copy, 0, 1, $path ); } else { splice( @copy, 0, 1 ); last; } } push( @all_positive_routes, @copy ); } ## Sort the possible positive routes my %sorted_count; foreach my $r (@all_positive_routes) { $sorted_count{ scalar(@$r) } = $r; } my @sorted = sort { $a <=> $b } keys(%sorted_count); ## If the INTERACTIVE=0, it will return only the shortest, ## possible positive route if ( $default == 0 ) { @all_positive_routes = ( \@{ $sorted_count{ shift(@sorted) } } ); } return \@all_positive_routes; } sub _findpaths { my $self = shift; ## _findpaths() routine will get all the combination paths. ## The inputs are empty_hashref, source_version and target_version. my ( $seen, $start, $end ) = @_; my %upgrade_versions = %{ $self->upgrade_versions }; ## Will return target_version if source_version is equal to target_version return [ [$end] ] if $start eq $end; ## Initialize the empty_hashref. $seen->{$start} = 1; my @paths; ## From the list of "Upgrade_Versions" hash get the combination paths. for my $node ( @{ $upgrade_versions{$start}->{'upgrades_to'} } ) { my %seen = %{$seen}; next if exists $seen{$node}; push @paths, [ $start, @$_ ] for @{ $self->_findpaths( \%seen, $node, +$end ) }; } return \@paths; } sub _andu { my $self = shift; $Log->step("Perform Happy Path ANDU Upgrade"); $Log->comment( 'The ontap dir is: ' . $self->ontap_dir ); NACL::STask::ClusterImage->update( command_interface => $self->{Cserver}, web_host => $self->{web_host}, ontap_dir => $self->{ontap_dir}, build_flags => $self->{build_flags}, arch => $self->{arch}, 'stabilize-minutes' => $self->{stabilize_minutes}, 'url' => $self->{package_url}, 'nodes' => $self->{subset_nodes}, 'force-rolling' => $self->{force_rolling}, 'method-timeout' => $self->{method_timeout}, ); } 1;