# # Copyright (c) 2001-2013 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary StorageDisk Task Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here package NACL::STask::StorageDisk; use strict; use warnings; use feature 'state'; use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use Params::Validate qw(:all); use NACL::C::Node; use NACL::CS::RaidConfigInfoListDisk; use NACL::C::Cluster; use NACL::CS::MetroclusterNode; use base qw(NACL::C::StorageDisk NACL::STask::STask); use NACL::ComponentUtils qw (_optional_scalars Dumper); use NACL::APISet::Exceptions::UnexpectedOutputException qw(:try); use NATE::Exceptions::Argument; use NACL::Exceptions::UnexpectedState; use NACL::STask::Exceptions::AllNodesDown; use NACL::C::Exceptions::NotInMCCConfig; use NATE::BaseException; use NATE::Time qw(timeout2time); use NACL::Transit; use NACL::GeneralUtils qw(nacl_method_retry); #TASK related constants; use constant DISK_REPLACE_TIMEOUT => 7200; use constant DISK_ZEROING_TIMEOUT => 7200; use constant POLL_DELTA => 30; =head1 NAME NACL::STask::StorageDisk =head1 DESCRIPTION C provides a number of well-defined but potentially complex or multi-step methods like fail(),unfail(), fail_multiple(),replace(),list(),check_cross_connect() and get_carrier_disks_mapping() related to Disk in ONTAP. It builds on top of, and is a derived class of C, and so it also provides methods that are more in the scope of individual disk-related commands. See C for details. This also means that a C object may generally be used in place of a component object. =head1 CLEANUP METHODS Cleanup can be registered for the following methods, Cleanup methods are, StorageDisk Method Cleanup Method ----------------------------------------------- online offline offline online =head1 ATTRIBUTES =head2 command_interface (Required) As C. A component object that represents the host to which to send commands. =head2 disk (Required) As C. The name of the Disk to be acted on. =cut =head1 FDR checks Certain methods, such as C, C, C and C support checking the FDR table (the "raid_config info showfdr" command is run on each node of the cluster which is up) to verify the success of the operation. If none of the nodes in the cluster are up, then a C exception is thrown. All of these methods support these options: =over =item C<< nacltask_check_fdr => 0|1 >> (Optional, defaults to 0) Specifies whether to check the FDR table. If run in an MCC environment, the FDR table check is performed on the partner cluster as well. =item C<< remote_interface => $remote_ci >> (Optional, isa CommandInterface object) If C is set to 1, this can be used to specify the command_interface of the partner cluster. If this is not specified, then the library will attempt to construct a command_interface for the partner cluster. =item C<< nacltask_check_fdr_local => 0|1 >> (Optional, defaults to 1) Specifies to check to FDR on B. =back Additionally, checking the FDR table can be done separately. See documentation for the methods L and L below. =head1 METHODS =head2 check_cross_connect NACL::STask::StorageDisk->check_cross_connect( command_interface => $command_interface node1 => $node1 node2 => $node2 %other_options); (Class method) Check to see if the disks of one node are visible to the other node in the HA pair by issuing NACL::C::StorageDisk->find(). For future enhancement, this method can check the cross connection on a cluster with multiple nodes. Currently the implementation will be on C-mode only. =over =item Options =over =item C<< command_interface => $command_interface >> (Required) See NACL::C::Component::command_interface =item C<< %other_options >> (Optional) All the other options supported by NACL::C::StorageDisk are supported. =back =back =over =item Exceptions =over =item C This type of exception is thrown when there is a failure to find the drives owned by either node1 or node2. =back =back =cut sub check_cross_connect { $Log->enter() if $may_enter; my $pkg = shift; $Log->trace( "Opts to 'check_cross_connect' front end are:\n" . Dumper( \@_ ) ); my %opts = validate @_, { command_interface => { isa => "NACL::C::CommandInterface" }, node1 => { type => SCALAR }, node2 => { type => SCALAR }, }; my $node1 = $opts{node1}; my $node2 = $opts{node2}; ### following 4 lines are reserved for future Cluster nodes implementation #my @cluster_nodes = NACL::C::Cluster->find(command_interface => $node); #foreach my $filer (@cluster_nodes) { # $Log->trace("Cluster Node " . $filer->node() ); #} my @nodes; push @nodes, NACL::C::Node->new( node => $node1 ); push @nodes, NACL::C::Node->new( node => $node2 ); my $nodename1 = $nodes[0]->node(); my $nodename2 = $nodes[1]->node(); my $test_cross_connect = sub { my ($owner) = @_; my %common_opts_with_ci; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_with_ci, ); my @disks_fetched_from_local; #fetch the disks of $owner from the first node. #occasionally command times out so retrying on failure nacl_method_retry( code => sub { @disks_fetched_from_local = NACL::CS::StorageDisk->fetch( filter => { owner => $owner, nodelist => [$nodename1], }, %common_opts_with_ci, ); }, exceptions => 'NACL::Exceptions::NoElementsFound', sleep_time => 10, tries_count => 6, ); my @disks_fetched_from_partner; #fetch the disks of $owner from the partner node. #occasionally command times out so retrying on failure nacl_method_retry( code => sub { @disks_fetched_from_partner = NACL::CS::StorageDisk->fetch( command_interface => $nodes[0], filter => { owner => $owner, nodelist => [$nodename2], } ); }, exceptions => 'NACL::Exceptions::NoElementsFound', sleep_time => 10, tries_count => 6, ); my @partner_disks = map { $_->uid() } @disks_fetched_from_partner; my @partner_disks_seen_from_local = map { $_->uid() } @disks_fetched_from_local; # All the disks as seen from the partner should be same as seen from the local node. foreach my $disks (@partner_disks) { unless ( grep /$disks/,@partner_disks_seen_from_local) { NATE::BaseException->throw( "$disks of $owner is not seen in the partner node"); } } }; $test_cross_connect->($nodename1); $test_cross_connect->($nodename2); $Log->exit() if $may_exit; } ## end sub check_cross_connect =head2 fail my $disk_obj = NACL::STask::StorageDisk->fail( command_interface => $command_interface, disk => $disk_name, immediate => $immediate, #default true ); (Class method) Fail the specified disk. This method defaults C to true. =over =item Options =over =item C<< disk => $disk_name >> (Required) Name of the disk to be failed. =item C<< immediate => $immediate >> (Optional) This option is helpful in case of failing the disk immediately or just marking it as sick disk. If C<$immediate> is C (the default), specified disk will be failed immediately. If C<$immediate> is C, then the disk is marked sick disk. =item FDR check options See the "FDR checks" section at the top of this page. =item command_interface, apiset_must, apiset_should, method-timeout, comment,disk etc. All of the other various options described in NACL::C::StorageDisk->fail =back =back =over =item Exceptions =over =item C This type of exception is thrown when storage disk fails as it is a filesystem disk which is not expected. =back =back =cut sub fail { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; state $additional_spec = {immediate => {type => BOOLEAN, default => 'true'},}; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => $additional_spec, allow_extra => 1, ); my $disk_name = $opts{disk}; my $command_interface = $opts{command_interface}; my %fdr_options; $pkg_or_obj->_move_fdr_options( source => \%opts, target => \%fdr_options, ); my %common_opts_with_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_with_ci, ); $pkg_or_obj->SUPER::fail(%opts); $pkg_or_obj->_check_show_fdr_fail( %common_opts_with_ci, %fdr_options, disks => [$disk_name], ); $Log->exit() if $may_exit; } ## end sub fail =head2 unfail NACL::STask::StorageDisk->unfail( command_interface => $command_interface, disk => $disk_name, spare => $spare, #default true ); or $disk_obj->unfail(spare => $spare); (Class/Instance method) Unfail the specified disk. This method provides the same functionality as the component unfail method, with option spare defaulted to 'true'. =over =item Options =over =item FDR check options See the "FDR checks" section at the top of this page. =item Component options All the options accepted by L are also accepted by this method. =back =back =cut sub unfail { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, allow_extra => 1, ); my %fdr_opts; $pkg_or_obj->_move_fdr_options( source => \%opts, target => \%fdr_opts, ); $pkg_or_obj->SUPER::unfail(spare => 'true', %opts); my %common_opts_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_ci, ); $pkg_or_obj->_check_show_fdr_unfail( %fdr_opts, %common_opts_ci, disks => [$opts{disk}], ); $Log->exit() if $may_exit; } =head2 fail_multiple my $disk_obj = NACL::STask::StorageDisk->fail_multiple( command_interface => $command_interface, disks => \@disks, immediate => $immediate, #default true continue_on_failure => $continue_on_failure, #default 0 ); (Class method) Fail the specified disks. This method provides additional services beyond what's inherent to the product's disk fail commands, as controlled and described by the "immediate" option. This also verifies the state of the disks after failing them. If C<$continue_on_failure> is not set to 1, then it throws exception on first failure of this operation. =over =item Options =over =item C<< disks => \@disks >> (Required) List of disk names to be failed. =item C<< immediate => $immediate >> (Optional) This option is helpful in case of failing the disks immediately or just marking them as sick disk. If C<$immediate> is C (the default), specified disks will be failed immediately. If C<$immediate> is C, then the disks are marked sick disk. =item C<< continue_on_failure => $continue_on_failure >> (Optional) This boolean value determines whether to continue disk-fail for all disks mentioned in C even if one of the operation fails. Default value is 0. =item FDR check options See the "FDR checks" section at the top of this page. =item command_interface, apiset_must, apiset_should, method-timeout, comment, disk etc. All of the other various options described in NACL::C::StorageDisk->fail_multiple =back =back =over =item Exceptions =over =item C This type of exception is thrown when no disk names are passed for the parameter 'disks'. OR This type of exception is thrown when there is a failure to fail the given disks. =back =back =cut sub fail_multiple { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; state $additional_spec = { disks => { type => ARRAYREF }, continue_on_failure => { type => BOOLEAN, default => 0 }, }; my %opts = $pkg->_common_validate_with( params => \@args, additional_spec => $additional_spec, ignore_primary_keys => 1, allow_extra => 1, ); my $disks = delete $opts{disks}; my $continue_on_failure = delete $opts{continue_on_failure}; my $command_interface = $opts{command_interface}; my %fdr_options; $pkg_or_obj->_move_fdr_options( source => \%opts, target => \%fdr_options, ); my @disk_operation_failed = (); if ( scalar( @{$disks} ) == 0 ) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("disks cannot be empty value."); } else { foreach my $disk ( @{$disks} ) { try { $pkg->fail( %opts, disk => $disk ); $Log->trace("Failed disk - $disk successfully"); } catch NACL::APISet::Exceptions::ResponseException with { my $exception = shift; if ($continue_on_failure) { $Log->trace( "Disk fail operation failed for $disk:" . $exception->text() ); push( @disk_operation_failed, $disk ); } else { $Log->exit() if $may_exit; $exception->throw(); } } ## end with } ## end foreach my $disk ( @{$disks...}) if ( scalar(@disk_operation_failed) ) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Following disks could not be failed: @disk_operation_failed" ); } ## end if ( scalar(@disk_operation_failed...)) } ## end else [ if ( scalar( @{$disks}...))] my %common_opts_ci; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_ci, ); $pkg->_check_show_fdr_fail( disks => $disks, %fdr_options, %common_opts_ci, ); $Log->exit() if $may_exit; } ## end sub fail_multiple =head2 replace my $disk_obj = NACL::STask::StorageDisk->replace( command_interface => $command_interface, disk => $disk_name, replacement => $replacement_disk_name, mix_disks => $mix_disks, #default 0 action => $action, #default start nacltask_wait => $nacltask_wait, #default 0 ); (Class method) Replace the specified disk with replacement disk. This method provides additional services beyond what's inherent to the product's disk replace commands, as controlled and described by the "mix_disks", "action" and "nacltask_wait" options. =over =item Options =over =item C<< disk => $disk_name >> (Required) Disk to be replaced. =item C<< replacement => $replacement_disk_name >> (Optional) Target disk for replacement. This is required if action is start. Otherwise, it is optional. =item C<< mix_disks => $mix_disks >> (Optional) This option is helpful in case of replacing disk by another disk with different characteristics. This allows replacing disk with different RPM or from different pool. =item C<< action => $action >> (Optional) This option helps in starting or stopping the disk replace. =item C<< nacltask_wait => $nacltask_wait >> (Optional) This option helps in waiting for replacement to complete. If $nacltask_wait is 0 (the default), disk replacement is started and returns without waiting for completion. If $nacltask_wait is 1, disk replacement is started and waits for completion. =item command_interface, apiset_must, apiset_should, method-timeout, comment, disk, replacement, action etc. All of the other various options described in NACL::C::StorageDisk->replace =back =back =over =item Exceptions =over =item C This type of exception is thrown when the specified action is invalid for disk replacement. =item C This type of exception is thrown when the disk is not a spare disk. =item C This type of exception is thrown when the disk is not a filesystem disk. =back =back =cut sub replace { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg->_common_validate_with( params => \@_, additional_spec => { replacement => { type => SCALAR, optional => 1 }, mix_disks => { type => BOOLEAN, default => 0 }, nacltask_wait => { type => BOOLEAN, default => 0 }, action => { type => SCALAR, default => "start" }, }, ); my $command_interface = $opts{command_interface}; my $disk = $opts{disk}; my $replacement = $opts{replacement}; my $nacltask_wait = delete $opts{nacltask_wait}; my $mix_disks = delete $opts{mix_disks}; my $action = $opts{action}; my $timeout = $opts{"method-timeout"}; if ( lc $action eq "start" ) { try { $pkg->SUPER::replace(%opts); } catch NACL::APISet::Exceptions::ResponseException with { # Check if filesystem disk, throw an exception if not my $source_disk_obj = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $command_interface, filter => { disk => $disk }, canned_filters => [qw(filesystem_disk)], allow_empty => 1 ); if (!defined $source_disk_obj) { $Log->exit() if $may_exit; NACL::Exceptions::UnexpectedState->throw( "Disk $disk is not a filesystem disk"); } # Check if the relacement disk is spare or not my $replacement_disk_obj = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $command_interface, filter => { disk => $replacement }, canned_filters => [qw(spare)], allow_empty => 1 ); if (!defined $replacement_disk_obj) { $Log->exit() if $may_exit; NACL::Exceptions::InvalidState->throw( "Disk $replacement is not a spare disk"); } }; if ($nacltask_wait) { Tharn::snooze(POLL_DELTA); $pkg->_wait_for_completion( command_interface => $command_interface, attribute_to_check => "copy_progress", expected_value => "-1", disks => [$disk], "method-timeout" => DISK_REPLACE_TIMEOUT ); } ## end if ($nacltask_wait) $Log->trace( "Replaced disk - $disk with replacement - $replacement successfully" ); } elsif ( lc $action eq "stop" ) { try { $pkg->SUPER::replace(%opts); $Log->trace("Disk Replace stopped"); } catch NACL::APISet::Exceptions::ResponseException with { # Check if filesystem disk, throw an exception if not my $source_disk_obj = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $command_interface, filter => { disk => $disk }, canned_filters => [qw(filesystem_disk)], allow_empty => 1 ); if (!defined $source_disk_obj) { $Log->exit() if $may_exit; NACL::Exceptions::UnexpectedState->throw( "Disk $disk is not a filesystem disk"); } }; } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Action $action is invalid for disk replacement"); } $Log->exit() if $may_exit; # returns nothing } ## end sub replace =head2 list B. This method is no longer supported and its usage is not recommended. Use L instead. Calls that currently use list() can be migrated to use NACL::CS::RaidConfigInfoListDisk as follows: 1) Filtering on multiple disks can be done by using the pipe ("|") symbol. A call like: my @disks = ( $disk1, $disk2, $disk3 ); my $arrayref = NACL::STask::StorageDisk->list( command_interface => $ci, disks => \@disks ); Can be replaced by: my @cs_objs = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $ci, filter => { disk => "$disk1|$disk2|$disk3" } ); 2) Filtering on any other attribute can be done by using the "filter". A call like: my $arrayref = NACL::STask::StorageDisk->list( command_interface => $ci, pool => $pool_name, fsm_state => $state ); Can be replaced by: my @cs_objs = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $ci, filter => { pool => $pool_name, fsm_state => $state }, ); The CS package provides numerous advantages over this method such as being able to use canned_filters and being able to use special characters in the filter for advanced filtering. See the documentation at L for more details. (Optional) Raid-tree name of disk. It also represents aggregate name for given disk =over =item Options =over =item C<< is-multidisk-carrier => $is_multidisk_carrier >> (Optional) Multidisk carrier Possible values - 0, 1. 1 represents carrier has 2 disks. =item C<< method-timeout => $timeout >> (Optional) timeout to list the disks Default - 120 sec =item C<< allow_empty=>$boolean >> (Optional) If false (the default) then throw an exception if no elements are found. If true, then return an empty list if no elements are found. =back =back =over =item Exceptions =over =item C This type of exception is thrown when there are no disks found with given attributes. =back =back =cut sub list { $Log->enter() if $may_enter; $Log->warn('The method NACL::STask::StorageDisk::list is deprecated. ' . 'This method is no longer supported and its usage is not ' . 'recommended. Use NACL::CS::RaidConfigInfoListDisk instead'); my ($pkg, @args) = @_; if ( ref($pkg) ) { die("NACL::STask::StorageDisk::list: This method is not an instance method" ); #TODO, Need an exception here } state $spec = { disks => { type => ARRAYREF | UNDEF, optional => 1 }, allow_empty => { type => SCALAR, default => 0 }, }; my %opts = validate_with( params => \@args, spec => $spec, # Other options are either common options (command_interface, # method-timeout etc) or are fields which will be passed along as # the filter. allow_extra => 1 ); my %fetch_opts = (allow_empty => delete $opts{allow_empty}); $pkg->_move_common_component_params_with_ci( source => \%opts, target => \%fetch_opts ); my $disks = delete $opts{disks}; my %disk_filter; $disk_filter{disk} = join ('|', @$disks) if defined $disks; my @disk_cs = NACL::CS::RaidConfigInfoListDisk->fetch( %fetch_opts, filter => {%opts, %disk_filter}, ); my @disk_attrs; foreach my $cs (@disk_cs) { # list() requires that hash-references are returned. Turn the blessed # objects into hash-references and remove the command_interface. my %hash = %$cs; delete $hash{command_interface}; push @disk_attrs, \%hash; } $Log->exit() if $may_exit; return \@disk_attrs; } ## end sub list #------------------------ =head2 _wait_for_completion my $disk_obj = NACL::STask::StorageDisk->_wait_for_completion( command_interface => $command_interface, disks => \@disks, attribute_to_check => $attribute_to_check, expected_value => $expected_value, ); (Class method) This methods waits for a required state on a set of disks by comparing the param with required value. =over =item Options =over =item C<< disks =>\@disks >> (Required) Disk names. =item C<< attribute_to_check =>$attribute_to_check >> (Required) parameter to be compared for wait operation. =item C<< expected_value =>$expected_value >> (Required) expected value of the param that is compared for wait operation. =item command_interface, apiset_must, apiset_should, method-timeout, comment etc. =back =back =cut sub _wait_for_completion { $Log->enter() if $may_enter; my ($pkg, @args) = @_; state $spec = { command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface' }, attribute_to_check => { type => SCALAR }, expected_value => { type => SCALAR }, disks => { type => ARRAYREF }, "method-timeout" => { type => SCALAR }, nacltask_polling_time => { type => SCALAR, default => POLL_DELTA }, }; my %opts = validate_with( params => \@args, spec => $spec, ); my $attribute_to_check = $opts{attribute_to_check}; my $expected_value = $opts{expected_value}; my $method_timeout = $opts{"method-timeout"}; my $end_time = timeout2time($method_timeout); my $cs; foreach my $disk (@{$opts{disks}}) { while (1) { try{ $cs = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $opts{command_interface}, filter => {disk => $disk}, ); }catch NACL::Exceptions::NoElementsFound with { my $error=shift; my $partner_name; try { my %common_opts; $pkg->_copy_common_component_params( source => \%opts, target => \%common_opts ); $partner_name = NACL::STask::Node->get_partner( command_interface => $opts{command_interface}, %common_opts, ); $cs = NACL::CS::RaidConfigInfoListDisk->fetch( command_interface => $opts{command_interface}, filter => {disk => $disk}, 'nodescope-node-name' => $partner_name, ); }catch NACL::Exceptions::HighAvailabilityError with { $error->throw(); }; }; my $done; try { $cs->verify_fields($attribute_to_check => $expected_value); $done = 1; } catch NACL::Exceptions::VerifyFailure with {}; last if $done; $Log->debug( sub { "Current value of $attribute_to_check is " . $cs->$attribute_to_check(); } ); if ( time() > $end_time ) { $Log->exit() if $may_exit; NACL::APISet::Exceptions::TimeoutException->throw( "Disk '$disk' did not reach the value " . "'$expected_value' for the attribute " . "'$attribute_to_check' after waiting for " . "$method_timeout seconds (its value is still '" . $cs->$attribute_to_check() . "')\n" ); } Tharn::snooze($opts{nacltask_polling_time}); } ## end while (1) } ## end foreach my $disk (@disks) $Log->exit() if $may_exit; } ## end sub _wait_for_completion =head2 wait_for_sdc_completion my $disk_obj = NACL::STask::StorageDisk->wait_for_sdc_completion( command_interface => $command_interface, disks => \@disks, ); Or $pkg->wait_for_sdc_completion(disks => \@disks,); (Class or instance method) This methods waits untill the soft disk copy completion =over =item Options =over =item C<< disks => ARRAYREF >> (Required) Disk names. =item FDR check options See the "FDR checks" section at the top of this page. =item command_interface, apiset_must, apiset_should, method-timeout, comment etc. =back =back =cut sub wait_for_sdc_completion { $Log->enter() if $may_enter; my $pkg = shift; my %opts = validate_with( params => \@_, spec => { command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface' }, disks => { type => ARRAYREF }, "method-timeout" => { type => SCALAR }, nacltask_polling_time => { type => SCALAR, default => POLL_DELTA }, }, # Other options are the FDR options allow_extra => 1, ); my $command_interface = $opts{command_interface}; my @disks = @{ $opts{disks} }; my $method_timeout = $opts{"method-timeout"}; my $nacltask_polling_time = $opts{nacltask_polling_time}; my %params = ( 'copy_progress' => '-1', 'prefail' => 0 ); my %fdr_opts; $pkg->_move_fdr_options( source => \%opts, target => \%fdr_opts ); foreach my $attribute ( keys %params ) { NACL::STask::StorageDisk->_wait_for_completion( command_interface => $command_interface, disks => \@disks, attribute_to_check => $attribute, expected_value => $params{$attribute}, nacltask_polling_time => $nacltask_polling_time, 'method-timeout' => $method_timeout ); } ## end foreach my $attribute ( keys...) $pkg->_check_show_fdr_fail( %fdr_opts, command_interface => $command_interface, disks => \@disks ); $Log->exit() if $may_exit; } ## end sub wait_for_sdc_completion =head2 get_carrier_disks_mapping my $carriers = NACL::STask::StorageDisk->get_carrier_disks_mapping( command_interface => $command_interface, spare => 1, forward => 1, ); @return: deep-hash values for disks and carrier mapping. If forward == 1: { carrier_id1 => [disk1, disk2], carrier_id2 => [disk3, disk4], } If forward == 0: { disk1 => carrier_id1, disk2 => carrier_id1, disk3 => carrier_id2, disk4 => carrier_id2, } (Class method) Get the disks and carrier mapping. This subroutine returns deep-hash values for disks and carrier mapping. If spare attribute is set to 1, then it returns disk-carrier mapping for only spare disks, else returns disk-carrier mapping for all disks. =over =item Options =over =item C<< forward => $forward >> (Optional) Customizes the return hash structure for carrier and disk name mapping. Possible values - 0, 1. 1 Maps carrier id to array of disks 0 Maps disk name to carrier id. Default values is 1. =item C<< spare => $spare >> (Optional) . Possible values - 0, 1. 1 returns carrier to disk mapping of only spare disks and 0 returns carrier to disk mapping for all disks. =item command_interface, apiset_must, apiset_should etc. All of the other various options are described in L. =back =back =cut sub get_carrier_disks_mapping { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = validate_with( params => \@_, spec => { command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface' }, spare => { type => SCALAR | UNDEF, optional => 1 }, forward => { type => SCALAR, default => 1, optional => 1 }, }, allow_extra => 1, ); my $forward = delete $opts{forward}; my $disks = $pkg->list( %opts, 'is-multidisk-carrier' => 1, ); my ( %carrier_disk_mapping, %disk_carrier_mapping ); foreach my $disk_obj (@$disks) { my $carrier = $disk_obj->{carrier}; my $disk_name = $disk_obj->{name}; if ( $forward == 0 ) { $disk_carrier_mapping{$disk_name} = $carrier; } else { if ( exists( $carrier_disk_mapping{$carrier} ) ) { push( @{ $carrier_disk_mapping{$carrier} }, $disk_name ); } else { $carrier_disk_mapping{$carrier} = [$disk_name]; } } ## end else [ if ( $forward == 0 ) ] } ## end foreach my $disk_obj (@$disks) $Log->exit() if $may_exit; if ( $forward == 0 ) { return ( \%disk_carrier_mapping ); } else { return ( \%carrier_disk_mapping ); } } ## end sub get_carrier_disks_mapping =head2 wait_for_zeroing_completion NACL::STask::StorageDisk->wait_for_zeroing_completion( command_interface => $ci, aggregate => $aggr_name, polling_interval => $polling_interval, ); or $storage_disk_stask_obj->wait_for_zeroing_completion( polling_interval => $polling_interval, 'method-timeout' => 900, ); (Class or instance method) Waits till all the disks provided are zeroed. Any one of the options (aggregate, all or disks) are mandatory. When invoked as an instance mathod, if the "disks" parameter is not specified, value stored in the instance variable - "disk" will be considered as a list of disks. =over =item Options =over =item C<< disks => arrayref >> (Optional) Array ref having a list of disks being zeroed. =item C<< all => boolean >> (Optional) With this optoin, method waits till all the disks being zeroed. =item C<< aggregate => scalar >> (Optional) With this option all the disks belonging to the given aggregate are zeroed. =item C<< polling_interval => scalar >> (Optional) Polling time interval, the default polling time is 20 seconds =item C<< method-timeout => scalar >> (Optional) Timeout value for wait. If not specified, timeout will be calculated based on data provided by the disk vendor. =back =back =over =item Exceptions =over =item C This type of exception is thrown when the values are not passed for anyone of the three options, ie (aggregate or all or disks) OR If the values are passed for more than one options. =item C This type of exception is thrown when there is failure in zeroing disks with in specified 'timeout' seconds =back =back =cut sub wait_for_zeroing_completion { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = $pkg_or_obj->get_package_name(); my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { disks => { type => ARRAYREF, optional => 1 }, all => { type => BOOLEAN, optional => 1 }, aggregate => { type => SCALAR, optional => 1 }, polling_interval => { type => SCALAR, default => 20 }, _optional_scalars qw(disk), }, ); if ( ( !$opts{disks} ) && ( defined $opts{disk} ) ) { $opts{disks} = [ $opts{disk} ]; } my $no_of_opts; foreach my $opt (qw(disks all aggregate)) { ++$no_of_opts if ( $opts{$opt} ); } if ( !$no_of_opts ) { $Log->exit() if $may_exit; NATE::BaseException->throw( "One of \"aggregate\",\"all\" or \"disks\" is mandatory!"); } if ( $no_of_opts > 1 ) { $Log->exit() if $may_exit; NATE::BaseException->throw( "Any one of \"aggregate\",\"all\" or \"disks\" can be specified!" ); } ## end if ( $no_of_opts > 1 ) my $polling_interval = $opts{polling_interval}; my $ci = $opts{command_interface}; my $apiset = $ci->get_7m_or_nodescope_apiset(); my $get_disk_state = sub { my $parsed_output = $apiset->raid_config( 'info' => 'listdisk', 'connectrec-timeout' => 90 )->get_parsed_output(); my %disk_state; foreach my $disk_info ( @{$parsed_output} ) { $disk_state{ $disk_info->{'name'} } = $disk_info->{'fsm_state'}; } return \%disk_state; }; my @zeroing_disks; if ( $opts{disks} ) { @zeroing_disks = @{ $opts{disks} }; } elsif ( $opts{all} ) { my $all_disks = &$get_disk_state(); while ( my ( $disk, $state ) = each %{$all_disks} ) { push @zeroing_disks, $disk if ( $state =~ /ZERO|NEEDFS/ ); } } elsif ( $opts{aggregate} ) { my @disks_info = NACL::CS::StorageDisk->fetch( command_interface => $ci, 'filter' => { 'aggregate' => $opts{aggregate} } ); foreach my $disk_info (@disks_info) { my $disk = $disk_info->disk(); $disk = $pkg->get_nodescope_diskname( 'command_interface' => $ci, 'disk' => $disk ); if ($disk_info->zeroed() =~ /^(false|\-)$/) { push @zeroing_disks, $disk; } } ## end foreach my $disk_info (@disks_info) } ## end elsif ( $opts{aggregate} ) my $timeout = $opts{'method-timeout'} || $pkg->_get_zeroing_estimate( command_interface => $ci, 'disklist' => \@zeroing_disks, ); $timeout += time(); while ( time() <= $timeout ) { my $zeroing = 0; my $disks_state = &$get_disk_state(); foreach my $disk (@zeroing_disks) { my $fsm_state = $disks_state->{$disk}; if ( $fsm_state eq "ZEROING" || $fsm_state eq "NOTZEROED" || $fsm_state =~ /NEEDFS/ ) { $Log->trace( "disk : " . $disk . " is being zeroed..." ); Tharn::snooze($polling_interval); $zeroing = 1; last; } ## end if ( $fsm_state eq "ZEROING"...) } ## end foreach my $disk (@zeroing_disks) if ( !$zeroing ) { $Log->exit() if $may_exit; return; } } ## end while ( time() <= $timeout) $Log->exit() if $may_exit; NACL::APISet::Exceptions::TimeoutException->throw( "Failed to wait for disks to get zeroed in $timeout seconds"); } ## end sub wait_for_zeroing_completion # Helper method to calculate the zeroing time based on the "zest" of the disks being zeroed sub _get_zeroing_estimate { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = $pkg_or_obj->get_package_name(); my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { disklist => { type => ARRAYREF, optional => 1 }, _optional_scalars qw(disk), }, ); # for disks on vsims, the zero estimate is always zero, hence defaulting it to DISK_ZEROING_TIMEOUT my $host_type = $opts{command_interface}->hostrec()->hosttype(); if ( $host_type =~ /vsim/i ) { $Log->exit() if $may_exit; return DISK_ZEROING_TIMEOUT; } my $disk_list; delete $opts{disk}; if ( $opts{disklist} ) { $disk_list = delete $opts{disklist}; } elsif ( ref $pkg_or_obj ) { $disk_list = [ $pkg_or_obj->disk() ]; delete $opts{disk}; } else { $disk_list = []; } my %fetch_args = ( %opts, requested_fields => [qw(state vendor model)] ); if ( !$disk_list ) { $fetch_args{'filter'} = { 'state' => "zeroing" }; } my @disks_info = NACL::CS::StorageDisk->fetch(%fetch_args); my ( @zest, $apiset_obj ); $apiset_obj = $opts{command_interface}->get_7m_or_nodescope_apiset(); my %disks; map { $disks{$_} = 1 } @{$disk_list} if $disk_list; foreach my $disk_info (@disks_info) { # If disk list is present, skip those disks which are not requested. if ($disk_list) { next if ( !$disks{ $disk_info->disk() } ); } # TODO - this is the old-style RAID state of a disk which will go away # should change to use the new way once its understood next if ( $disk_info->{state} =~ /present/ ); # TODO - we have to rely on nodescope/7Mode command 'storage show qualtable' # did not find an equivalent ngshell command to get zest $apiset_obj = $opts{command_interface}->get_7m_or_nodescope_apiset(); my $response_obj = $apiset_obj->storage_show_qualtable( 'vendor-id' => $disk_info->vendor(), 'product-id' => $disk_info->model() ); my $parsed_output = $response_obj->get_parsed_output(); my $qual_table_info = $parsed_output->[1]; if ( defined $qual_table_info->{'zest'} ) { push @zest, $qual_table_info->{'zest'}; } } ## end foreach my $disk_info (@disks_info) $Log->exit() if $may_exit; # In cases where the disks belonging to to a given aggregate are already in # the present state, the @zest will be null. In such cases return DISK_ZEROING_TIMEOUT if ( scalar @zest == 0 ); return ( sort { $a <=> $b } @zest )[-1]; } ## end sub _get_zeroing_estimate =head2 offline NACL::STask::StorageDisk->offline( command_interface => $command_interface, disks => $disk_name, nacltask_to_cleanup => 1, # default '0' nacltask_cleanup_manager => $cleanupobj, ); (Class call) or my $disk_obj = NACL::STask::StorageDisk->new( command_interface => $command_interface, disk => $disk_name, ); $disk_obj->offline(); (Instance call) (Class or Instance method) offline the specified disk. This method provides additional services beyond what's inherent to the product's disk offline commands as user can online the multiple disk in single call. And also provides an mechanism of cleanup for the objects that is offlined by this method. =over =item Options =over =item C<< disks => ARRAYREF|SCALAR >> (Optional) Array reference of disks name or single disk to be offline. This parameter can be passed only when the method is a Class call. =item C<< type-option => SCALAR >> (Optional) Type of disk =item C<< disk => SCALAR >> (Optional) The name of the disk to be offlined. This parameter can be passed for both class and Instance call. =item C<< nacltask_to_cleanup => 0|1 >> (Optional, default to 0(no to cleanup)) Flag indicating if this operation needs to be registered for clean up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to be used for registering. Default :will use the default cleanup manager. =item command_interface, apiset_must, apiset_should, method-timeout, comment,disk etc. All of the other various options described in NACL::C::StorageDisk->offline =back =back =cut sub offline { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface::ONTAP'}, disks => { type => ARRAYREF | SCALAR, optional => 1 }, disk => { type => SCALAR, optional => 1 }, 'type-option' => { type => SCALAR, optional => 1 }, $pkg_or_obj->_cleanup_validate_spec(), }, ); $opts{operation} = "offline"; $Log->exit() if $may_exit; return _offline_online($pkg_or_obj, %opts); } ## end sub offline =head2 online NACL::STask::StorageDisk->online( command_interface => $command_interface, disks => $disk_name nacltask_to_cleanup => 1, # default '0' nacltask_cleanup_manager => $cleanupobj, ); (Class call) or my $disk_obj = NACL::STask::StorageDisk->new( command_interface => $command_interface, disk => $disk_name, ); $disk_obj->online(); (Instance call) (Class or Instance method) Online the specified disk. This method provides additional services beyond what's inherent to the product's disk online commands as user can online the multiple disk in single call. And also provides an mechanism of cleanup for the objects that is onlined by this method. =over =item Options =over =item C<< disks => ARRAYREF|SCALAR >> (Optional) Array reference of disks name or single disk to be onlined. This parameter can be passed only when the method is a Class call. =item C<< disk => SCALAR >> (Optional) The name of the disk to be onlined. This parameter can be passed for both class and Instance call. =item C<< error => SCALAR >> (Optional) Type of disk =item command_interface, apiset_must, apiset_should, method-timeout, comment,disk etc. All of the other various options described in NACL::C::StorageDisk->online =item C<< nacltask_to_cleanup => 0|1 >> (Optional, default to 0(no to cleanup)) Flag indicating if this operation needs to be registered for clean up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to be used for registering. Default :will use the default cleanup manager. =back =back =cut sub online { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface::ONTAP' }, disks => { type => ARRAYREF | SCALAR, optional => 1 }, disk => { type => SCALAR, optional => 1 }, error => { type => SCALAR, optional => 1 }, $pkg_or_obj->_cleanup_validate_spec(), }, ); $opts{operation} = "online"; $Log->exit() if $may_exit; return _offline_online($pkg_or_obj, %opts); } ## end sub online =head2 remove_ownership NACL::STask::StorageDisk->remove_ownership( command_interface => $command_interface, disks => \@disks, continue_on_failure => $continue_on_failure, #default 0 ); (Class method) Remove the ownership of the specified disk. This method provides additional services beyond what's inherent to the product's disk remove_ownership command as user can remove the ownership of the multiple disks in single call. If C<$continue_on_failure> is not set to 1, then it throws exception on first failure of this operation. =over =item Options =over =item C<< disks => \@disks >> (Required) List of disk names to be failed. =item C<< continue_on_failure => $continue_on_failure >> (Optional) This boolean value determines whether to continue disk remove-ownership for all disks mentioned in C even if one of the operation fails. Default value is 0. =item command_interface, apiset_must, apiset_should, method-timeout, comment,disk etc. All of the other various options described in NACL::C::StorageDisk->removeowner =back =back =cut sub remove_ownership { $Log->enter() if $may_enter; my $pkg = shift; my %opts = $pkg->_common_validate_with( params => \@_, additional_spec => { disks => { type => ARRAYREF }, continue_on_failure => { type => BOOLEAN, default => 0 }, }, ignore_primary_keys => 1, allow_extra => 1, ); my $disks = delete $opts{disks}; my $command_interface = $opts{command_interface}; my $continue_on_failure = delete $opts{continue_on_failure}; my @disk_operation_failed = (); if ( scalar( @{$disks} ) == 0 ) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("disks cannot be empty value."); } else { foreach my $disk ( @{$disks} ) { try { $pkg->SUPER::removeowner( %opts, disk => $disk, force => 'true' ); $Log->trace("Onwer removed for disk - $disk successfully"); } catch NACL::APISet::Exceptions::ResponseException with { my $exception = shift; if ($continue_on_failure) { $Log->trace( "Disk remove-ownership operation failed for $disk:" . $exception->text() ); push( @disk_operation_failed, $disk ); } else { $Log->exit() if $may_exit; $exception->throw(); } } ## end with } ## end foreach my $disk ( @{$disks...}) if ( scalar(@disk_operation_failed) ) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Ownership cannot be remove for following disks: @disk_operation_failed" ); } ## end if ( scalar(@disk_operation_failed...)) } ## end else [ if ( scalar( @{$disks}...))] $Log->exit() if $may_exit; } sub _verify_fdr_table { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; state $additional_spec = { _operation => {type => SCALAR}, serialno_disk_hash => {type => HASHREF}, }; my %opts = validate_with( params => \@args, spec => $additional_spec, allow_extra => 1 ); my $operation = $opts{_operation}; my $serialno_disk_hash = $opts{serialno_disk_hash}; my @serial_numbers = keys %$serialno_disk_hash; my $command_interface = $opts{command_interface}; my @all_nodes_in_ci = NACL::C::Cluster ->node_objs(command_interface=>$command_interface); my ($node_name,%failed_disks_serialno,@all_nodes_up); foreach my $check_n (@all_nodes_in_ci) { my $transit_obj = NACL::Transit->new(name => $check_n->node() ); if ( ($transit_obj->get_state()) !~ /FIRMWARE|LOADER|BOOT_MENU/ ) { push @all_nodes_up,$check_n ; } } if(!@all_nodes_up) { $Log->exit() if $may_exit; NACL::STask::Exceptions::AllNodesDown ->throw("None of the nodes in the cluster are up"); } my $nodescope_apiset = $command_interface->get_7m_or_nodescope_apiset(); foreach my $this_node (@all_nodes_up) { my $parsed_output = $nodescope_apiset->raid_config( 'info' => 'showfdr', 'nodescope-node-name' => $this_node->node(), )->get_parsed_output(); %failed_disks_serialno = ( %failed_disks_serialno, map {$_->{serialno} => 1} @$parsed_output ); } foreach my $serialno (@serial_numbers) { if ($operation eq 'fail' && !exists $failed_disks_serialno{$serialno}) { $Log->exit() if $may_exit; NATE::BaseException->throw("The disk '" . "$serialno_disk_hash->{$serialno}' should have been " . 'failed but an FDR entry for it was not present on ' . "node '$node_name'" ); } elsif ($operation eq 'unfail' && exists $failed_disks_serialno{$serialno}) { $Log->exit() if $may_exit; NATE::BaseException->throw("The disk '" . "$serialno_disk_hash->{$serialno}' should have been " . 'unfailed but an FDR entry for it was present on ' . "node '$node_name'" ); } } $Log->exit() if $may_exit; } sub _check_show_fdr { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; # Has already gone through _common_validate_with, so no need to use # it again; can just use validate_with. state $additional_spec = { %{$pkg_or_obj->_fdr_validate_spec()}, disks => {type => ARRAYREF} }; my %opts = validate_with( params => \@args, spec => $additional_spec, # Let _verify_fdr_table validate the options it accepts allow_extra => 1, ); my $check_fdr = delete $opts{nacltask_check_fdr}; my $check_fdr_local = delete $opts{nacltask_check_fdr_local}; if ($check_fdr || $check_fdr_local) { my $remote_interface = delete $opts{remote_interface}; my $disk_hash_ref = $pkg_or_obj->get_package_name()->get_ngsh_diskname( 'command_interface' => $opts{command_interface}, 'disks' => $opts{disks}, ); my @disks_passed = @{$opts{disks}}; my @resolved_disks = map { $disk_hash_ref->{$_} } @{delete $opts{disks}}; my $disk_filter = join('|', @resolved_disks, @disks_passed); my %common_opts_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_ci ); # Get the serialno. of disks to be failed # Serialno. is common on both local and remote clusters, so # determine the value once and then pass it into both # calls to _verify_fdr_table. my @disk_cs_obj = NACL::CS::StorageDisk->fetch( filter => {disk => $disk_filter}, requested_fields => [qw(serial-number)], %common_opts_ci, ); my %serialno_disk_hash = map {$_->serial_number() => $_->disk()} @disk_cs_obj; $pkg_or_obj->_verify_fdr_table(%opts, serialno_disk_hash => \%serialno_disk_hash); if ( $check_fdr ) { if (!defined $remote_interface) { try { $remote_interface = $common_opts_ci{command_interface} ->get_remote_interface_mcc(%common_opts_ci); } catch NACL::C::Exceptions::NotInMCCConfig with { $Log->trace('Not an MCC environment'); }; if(defined $remote_interface) { $pkg_or_obj->_verify_fdr_table( %opts, command_interface => $remote_interface, serialno_disk_hash => \%serialno_disk_hash ); } } } } } =head2 check_fdr_fail # Package call for a single disk. NACL::STask::StorageDisk->check_fdr_fail( command_interface => $ci, disk => $disk_name, %fdr_options ); # Instance call for a single disk. $disk_obj->check_fdr_fail(%fdr_options); # Call for multiple disks (can be done only with a package call). NACL::STask::StorageDisk->check_fdr_fail( command_interface => $ci, disks => [$disk_name1, $disk_name2, ...], %fdr_options ); This method can be used to check the FDR table for whether the specified disk or disks have been failed. The options accepted by this method are described in the "FDR checks" section at the beginning of this page. =cut sub check_fdr_fail { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my $opts = $pkg_or_obj->_validate_construct_fdr_opts(@args, _operation => 'fail'); $pkg_or_obj->_check_show_fdr_fail(%$opts); $Log->exit() if $may_exit; } =head2 check_fdr_unfail This method can be used to check the FDR table for whether the specified disk or disks have been unfailed. The interface of this method and the options supported are the same as L. =cut sub check_fdr_unfail { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my $opts = $pkg_or_obj->_validate_construct_fdr_opts(@args, _operation => 'unfail'); $pkg_or_obj->_check_show_fdr_unfail(%$opts); $Log->exit() if $may_exit; } # Determine the value of "disks" (if not passed, then use "disk"); fail # if neither disk/disks passed. Also default nacltask_check_fdr to 1 # IFF nacltask_check_fdr_local has not been set to 1. sub _validate_construct_fdr_opts { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; state $additional_spec = { %{$pkg_or_obj->_fdr_validate_spec()}, _operation => {type => SCALAR}, # Either of disks/disk should be passed. disks => {type => ARRAYREF, optional => 1}, disk => {type => SCALAR, optional => 1}, }; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => $additional_spec, # Let _verify_fdr_table validate the options it accepts allow_extra => 1, ); # From this point onwards, "disks" is used, not "disk", so remove it from %opts. my $disk = delete $opts{disk}; # If "disks" is not supplied, default it to using the value supplied # through "disk". $opts{disks} ||= [$disk]; # If neither are passed, then $opts{disks} will be undef, so fail if (!defined $opts{disks}) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("Either 'disk' or 'disks' should " . 'be provided in the call to check_show_fdr_' . "$opts{_operation} but neither were." ); } if (!$opts{nacltask_check_fdr} && !$opts{nacltask_check_fdr_local}) { # Default nacltask_check_fdr to 1 if nacltask_check_fdr_local is # also not passed. $opts{nacltask_check_fdr} = 1; } $Log->exit() if $may_exit; # Quicker to return a reference return \%opts; } sub _check_show_fdr_fail { $Log->enter() if $may_enter; my ($pkg_or_obj, @opts) = @_; $pkg_or_obj->_check_show_fdr(@opts, _operation => 'fail'); $Log->exit() if $may_exit; } sub _check_show_fdr_unfail { $Log->enter() if $may_enter; my ($pkg_or_obj, @opts) = @_; $pkg_or_obj->_check_show_fdr(@opts, _operation => 'unfail'); $Log->exit() if $may_exit; } sub _fdr_validate_spec { $Log->enter() if $may_enter; my $pkg_or_obj = $_[0]; state $ontap_ci_spec = {%{$pkg_or_obj->_ontap_ci_validate_spec()}, optional => 1}; state $spec = { nacltask_check_fdr => {type => BOOLEAN, default => 0}, nacltask_check_fdr_local => {type => BOOLEAN, default => 0}, remote_interface => $ontap_ci_spec, }; $Log->exit() if $may_exit; return $spec; } sub _move_fdr_options { $Log->enter() if $may_enter; my ($pkg_or_obj, @opts) = @_; state $move = [keys %{$pkg_or_obj->_fdr_validate_spec()}]; $pkg_or_obj->_hash_move(@opts, move => $move); $Log->exit() if $may_exit; } sub _offline_online { $Log->enter() if $may_enter; my ($pkg_or_obj, %opts) = @_; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my ( $response, $attributes, $status, $to_cleanup, %apiset_opts ); if ($opts{disk} && $opts{disks}) { NATE::Exceptions::Argument->throw( "Either 'disk' or 'disks' can be provided as arguments, " ."but cannot provide both together" ); } if (!$opts{disk} && !$opts{disks}) { NATE::Exceptions::Argument->throw( "Either 'disk' or 'disks' should be provided as arguments" ); } my $operation = delete $opts{operation}; my $command_interface = $opts{command_interface}; my $disk_opt = $opts{disks} ? $opts{disks} : $opts{disk}; my @disks = ref($disk_opt) ? @{$disk_opt} : ($disk_opt); my (%opts_for_cleanup, %opts_for_register, $nacltask_to_cleanup ); $pkg->_move_common_cleanup_opts( source => \%opts, target => \%opts_for_cleanup, ); my $disks_as_in_7m_or_nodescope = $pkg->get_nodescope_diskname( 'command_interface' => $command_interface, 'disks' => \@disks ); $apiset_opts{'type-option'} = $opts{'type-option'} if $opts{'type-option'}; $apiset_opts{error} = $opts{error} if $opts{error}; my $nodescope_apiset = $command_interface->get_7m_or_nodescope_apiset(); foreach my $disk (values %{$disks_as_in_7m_or_nodescope}) { if ($operation eq "offline"){ $response = $nodescope_apiset->disk_offline( disk => $disk, %apiset_opts ); $to_cleanup = "online"; } else { $response = $nodescope_apiset->disk_online( disk => $disk, %apiset_opts ); $to_cleanup = "offline"; } $attributes = $response->get_parsed_output(); my $status = $attributes->[0]->{status}; $Log->debug("Disk $disk state is $status\n"); if ( $status !~ /$operation/ ) { $Log->exit() if $may_exit; NACL::Exceptions::UnexpectedState->throw( "Disk $disk is not in $operation state"); } # Register this resource with the cleanup manager for cleanup. $opts{disk} = $disk; $pkg->_copy_common_opts_for_cleanup( 'source' => {%opts, %opts_for_cleanup}, 'target' => \%opts_for_register, 'nacltask_to_cleanup'=> \$nacltask_to_cleanup, 'to_cleanup' => $to_cleanup, ); $pkg->_register_for_cleanup(%opts_for_register) if ($nacltask_to_cleanup); } ## end foreach my $disk (@disks) $Log->exit() if $may_exit; return $pkg->new( command_interface => $opts{command_interface}, disk => $opts{disk} ) if ($opts{disk}); } ## end sub _offline_online 1;