# # Copyright (c) 2014-2015 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary Prepare Task Module ## @author ppranav@netapp.com ## @status ## @pod here package NACL::MTask::Prepare; use strict; use warnings; use Tharn qw( host); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use Exporter qw(import); use Params::Validate qw(:all); use NATE::ProcManager; use NACL::C::StorageFailover; use NACL::CS::StorageFailover; use NACL::C::StorageDiskOption; use NATE::BaseException qw(:try); use NATE::Exceptions::Argument; use vars qw( $VERSION @Nodes %SModeConfig ); $VERSION = '0.01'; our @EXPORT_OK = qw(prepare_start get_node_config); =head1 NAME NACL::MTask::Prepare =head1 DESCRIPTION C provides required functionality to prepare nodes for CFT transition. Functionality provided by this library are used before Cabling and Import phase in CFT transition work flow. This lib performs following operations. 1. Find HA partner 2. Capture both node configuration 3. Disable HA 4. Disable Protocol configuration i.e. nfs, cifs,etc. 5. Disable Disk auto assign 6. Boot to maint mode 7. Destroy mailbox 8. Remove disk ownership Nodes are in maintenance mode after prepare finishes. This module will automatically find the Ha partner and execute prepare for partner node also. This modules works for single node also. Unassigned disks are not considered there for all the disks should be assigned before using this library module =head2 prepare_start Method Invocation via Class: $NodeConfigHash = NACL::MTask::Prepare->prepare_start( command_interface => $Node7Mode[0] ); This method performs all the steps required to be performed in prepare phase for CFT transition. =over =item Options =over =item C<< command_interface => $command_interface >> (Required) This is required parameter, this parameter provides reference to one of the node in HA pair. It finds the other partner node and performs all the operation on that node as well. =back =cut sub prepare_start { my ( $self, %args ) = @_; my (%opts); %opts = validate_with( params => \%args, spec => $self->_prepare_validate_spec(), allow_extra => 0 ); if ( !%opts ) { NATE::Exceptions::Argument->throw("Need mandatory arguments."); } _prepare_7mode_config( $args{'command_interface'} ); #Sleeping to let msgs because of show command pass. sleep 5; if ( scalar(@Nodes) == 1 ) { _prepare_node( $Nodes[0], $opts{type} ); } else { my $procmgr = NATE::ProcManager->new( proc_info => [ { codespec => \&_prepare_node, args => [ $Nodes[0], $opts{type} ], runid => "Prepare node " . $Nodes[0]->name(), onexit => \&NATE::Process::on_exit_propagate_worst_result }, { codespec => \&_prepare_node, args => [ $Nodes[1], $opts{type} ], runid => "Prepare node " . $Nodes[1]->name(), onexit => \&NATE::Process::on_exit_propagate_worst_result }, ], background => 1, parallel => 1, #iterate_individually => 1, ); $procmgr->start; $procmgr->wait; my $result = NATE::Result->is_failure( $procmgr->worst_result ); if ($result) { NATE::BaseException->throw("Failed to perform prepare operations"); } } return \%SModeConfig; } =head2 get_node_config Method Invocation via Class: NACL::MTask::Prepare->get_node_config( command_interface => $command_interface, ); This method returns a hash that contains all important configuration parameters for given node in as command_interface and the HA partner node including information of the disks. Unassigned disks are not considered there for all the disks should be assigned before using this library module =over =item Options =over =item C<< command_interface => $command_interface >> (Required) This is required parameter, this parameter provides reference to one of the node in HA pair. It finds the other partner node and return values for that node as well. =back =cut sub get_node_config { my ( $self, %args ) = @_; my ( %opts, ); if ( ( ref($self) =~ /Prepare/i ) && ( exists $self->{'command_interface'} ) ) { $args{'command_interface'} = $self->{'command_interface'}; } %opts = validate_with( params => \%args, spec => $self->_prepare_validate_spec(), allow_extra => 0 ); if ( !%opts ) { NATE::Exceptions::Argument->throw("Need mandatory arguments."); } _prepare_7mode_config( $args{'command_interface'} ); return \%SModeConfig; } ###################################################################### ######################### HELPER ROUTINES ############################ ###################################################################### sub _prepare_validate_spec { $Log->enter() if $may_enter; my %input_validate_spec = ( command_interface => { type => ARRAYREF }, type => { type => SCALAR, optional => 1, default => "CFT" }, ); $Log->exit() if $may_exit; return \%input_validate_spec; } sub _prepare_node { my $node = shift; my $type = shift; my @spareDisks; my $api_set_obj = $node->get_7m_or_nodescope_apiset(); my $parsed_output; my @aggr_list; my $resp; # IPT TASKS if ( $type eq 'IPT' ) { my $spareHash = _get_spare_disks_on_apiset($api_set_obj); foreach ( keys %{$spareHash} ) { push( @spareDisks, $spareHash->{$_}->{'device'} ); } if ( @spareDisks < 3 ) { NATE::BaseException->throw( "Cannot Proceed :: Sufficient Spare disks are not available on Node: " . $node->name() ); } } my $sf_state = NACL::CS::StorageFailover->fetch( command_interface => $node ); if ( $sf_state->cluster_monitor() =~ m/enabled/i ) { NACL::C::StorageFailover->modify( command_interface => $node, node => $node->name(), enabled => 'false' ); } NACL::C::StorageDiskOption->modify( command_interface => $node, node => $node->name(), "autoassign" => "off", ); my @cmds = ( "options cf.takeover.on_reboot off", "nfs off", "cifs terminate", "fcp stop", "iscsi stop" ); foreach my $cmd (@cmds) { eval { $api_set_obj->execute_command( command => "$cmd", ); }; } $resp = $api_set_obj->execute_command( command => "nfs status" ); if ( $resp =~ /service is running/i ) { NATE::BaseException->throw( "Something went wrong. NFS service is " . "still running." ); } $resp = $api_set_obj->execute_command( command => "cifs status" ); if ( $resp =~ /service is running/i ) { NATE::BaseException->throw( "Something went wrong. CIFS service is " . "still running." ); } $resp = $api_set_obj->execute_command( command => "fcp status" ); if ( $resp =~ /service is running/i ) { NATE::BaseException->throw( "Something went wrong. FCP service is " . "still running." ); } $resp = $api_set_obj->execute_command( command => "iscsi status" ); if ( $resp =~ /service is running/i ) { NATE::BaseException->throw( "Something went wrong. iSCSI service is " . "still running ." ); } $parsed_output = $api_set_obj->aggr_status()->get_parsed_output(); foreach my $entry (@$parsed_output) { push( @aggr_list, $entry->{aggregate} ); } _set_bootargs($node); # IPT TASKS if ( $type eq 'IPT' ) { foreach my $aggr_entry (@aggr_list) { _exec_cmd_maint_mode( $node, "aggr offline " . $aggr_entry ); } } #Destroying mailbox foreach my $cmd ( "mailbox destroy local", "storage release disks -f", "disk show", "disk show -n", "disk show -v -z" ) { _exec_cmd_maint_mode( $node, $cmd ); } # Removing disk-ownerships _exec_cmd_maint_mode( $node, "disk remove_ownership" ); # IPT TASKS if ( $type eq 'IPT' ) { my $node_name = $node->name(); my $api_maint = _get_maint_mode($node); my @diskToAssign = @spareDisks[ 0, 1, 2 ]; my $sysid = $SModeConfig{$node_name}{'system_id'}; _assign_disks_on_apiset( apiset => $api_maint, disk_list => \@diskToAssign, sysid => $sysid, 'owner' => $node_name, ); } foreach my $cmd ( "disk show", "disk show -n", "disk show -v -z" ) { _exec_cmd_maint_mode( $node, $cmd ); } } sub _prepare_7mode_config { my $ci = shift; if ( scalar( @{$ci} ) == 1 ) { _get_partner( $ci->[0] ); } else { foreach ( @{$ci} ) { push( @Nodes, $_ ); } } _prepare_disk_list(); foreach my $node (@Nodes) { my $api_set_obj = $node->get_7m_or_nodescope_apiset(); my $parsed_output; my @aggr_list; $parsed_output = $api_set_obj->aggr_status( verbose => 1 )->get_parsed_output(); foreach my $entry (@$parsed_output) { $SModeConfig{ $node->name() }{aggregates}{ $entry->{aggregate} } = $entry; my $aggr_cs = NACL::CS::Aggregate->fetch( command_interface => $node, filter => { aggregate => $entry->{aggregate} }, requested_fields => [qw(disklist)] ); $SModeConfig{ $node->name() }{aggregates}{ $entry->{aggregate} } {aggr_disks} = $aggr_cs->disklist(); } $parsed_output = $api_set_obj->snap_list( aggregate => 1 )->get_parsed_output(); foreach my $entry (@$parsed_output) { $SModeConfig{ $node->name() }{aggregates}{ $entry->{aggregate} } {snapshot_info} = $entry->{snapshot_info}; } $parsed_output = $api_set_obj->vol_status( verbose => 1 )->get_parsed_output(); foreach my $entry (@$parsed_output) { $SModeConfig{ $node->name() }{volumes}{ $entry->{volume} } = $entry; } $parsed_output = $api_set_obj->snap_list( volume => 1 )->get_parsed_output(); foreach my $entry (@$parsed_output) { $SModeConfig{ $node->name() }{volumes}{ $entry->{volume} } {snapshot_info} = $entry->{snapshot_info}; } my $node_name = $node->name(); my $disk_entry = $SModeConfig{$node_name}{disks}[0]; $disk_entry->{owner} =~ /$node_name\((.*)\)/; my $sys_id = $1; $SModeConfig{$node_name}{'system_id'} = $sys_id; my $host_obj = host( $node->name() ); my $apiset_sys = NACL::APISet->new( hostobj => $host_obj, category => "Node", interface => "CLI", set => "Systemshell", ); my $resp = $apiset_sys->execute_command( command => "cat /VERSION" ); $resp =~ /.*(\d+\.\d+)\.\d+/; $resp = $1; $resp =~ s/\./_/; $SModeConfig{ $node->name() }{kernal_version} = "kern_vers_" . $resp; } } sub _prepare_disk_list { foreach my $node (@Nodes) { my $api_set_obj = $node->get_7m_or_nodescope_apiset(); my $parsed_output = $api_set_obj->disk_show( 'owner-name' => $node->name() ) ->get_parsed_output(); $SModeConfig{ $node->name() }{disks} = $parsed_output; $SModeConfig{ $node->name() }{spare_disks} = _get_spare_disks_on_apiset($api_set_obj); } } # Method to mute console messages in maintenance mode sub _set_bootargs { my $node = shift; try { NACL::Transit->new( name => $node->name() )->change_state( to => 'FIRMWARE' ); my $apiset = $node->apiset(set => "Firmware"); $apiset->setenv( varname => 'AUTOBOOT', value => 'true' ); $apiset->setenv( varname => 'bootarg.init.console_muted', value => 'true' ); $apiset->setenv( varname => 'bootarg.init.console_level', value => '"-1"' ); $apiset->saveenv(); } catch NATE::BaseException with { my $exception = shift; }; } sub _get_maint_mode { my $node = shift; my $Transit_obj = NACL::Transit->new( name => $node->name() ); my $original_state = $Transit_obj->get_state(); $Transit_obj->change_state( to => 'MAINT' ); my $host = host( $node->name() ); my $api_set_obj = NACL::APISet->new( hostobj => $host, category => "Node", interface => "CLI", set => "Maintenance", ); $api_set_obj->set_timeout( 'timeout' => 120 ); return $api_set_obj; } sub _exec_cmd_maint_mode { my $node = shift; my $cmd = shift; my $api = _get_maint_mode($node); my $prompts = [ "Are you sure you want to destroy the local mailboxes?" => "yes", "Do you wish to continue?" => "yes", "Volumes must be taken offline. Are all impacted volumes offline(y/n)?" => "yes", "Do you want to continue(y/n)?" => "yes", "Are you sure you want to destroy this aggregate?" => "yes", "Are you sure want to take .*?" => "yes", ]; my $res = $api->execute_command( 'command' => $cmd, 'connectrec-match_table' => $prompts, ); return $res; } sub _get_partner { my $ci = shift; @Nodes = (); push( @Nodes, $ci ); my $sf_state = NACL::CS::StorageFailover->fetch( command_interface => $ci ); if ( $sf_state->partner_name() ) { $Log->comment( "Also executing in parallel for HA partner " . $sf_state->partner_name() ); } else { $Log->comment("Unable to find HA partner. Executing for single node"); return; } my $hostrec = host( $sf_state->partner_name() ); my $other_node_obj = NACL::C::Node->new( name => $sf_state->partner_name(), hostrec => $hostrec ); push( @Nodes, $other_node_obj ); } sub _get_spare_disks_on_apiset { my $api_set_obj = shift; my %spare_disks; my $parsed_output = $api_set_obj->aggr_status( 'spare-disks' => 1 )->get_parsed_output(); foreach my $disk_entry (@$parsed_output) { if ( !exists $disk_entry->{device} ) { foreach my $hash_key ( keys $disk_entry ) { foreach my $disk_info ( @{ $disk_entry->{$hash_key} } ) { my $disk = $disk_info->{device}; my $output = $api_set_obj->disk_show( disk => $disk ) ->get_parsed_output(); $spare_disks{ $output->[0]->{serial_number} } = $disk_info; } } } else { my $disk = $disk_entry->{device}; my $output = $api_set_obj->disk_show( disk => $disk )->get_parsed_output(); $spare_disks{ $output->[0]->{serial_number} } = $disk_entry; } } return \%spare_disks; } sub _assign_disks_on_apiset { my %args = @_; my $apiset = $args{apiset}; my $disk_list = $args{disk_list}; my $sysid = $args{sysid}; my $owner = $args{owner}; foreach ( @{$disk_list} ) { $apiset->disk_assign( 'disk-list' => $_, 'force' => 1, 'sysid' => $sysid, 'owner-name' => $owner ); } } 1;