# # Copyright (c) 2001-2016 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary VolumeQuota STask module ## @author swati.bajaj@netapp.com, dl-nacl-dev@netapp.com ## @status shared ## @pod here package NACL::STask::VolumeQuota; use strict; use warnings; use base qw(NACL::C::VolumeQuota NACL::STask::STask); 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(validate :types); use NACL::ComponentUtils qw(_optional_scalars); use NACL::CS::Statistics; use NACL::STask::Exceptions::VolumeQuotaReportVerify; use NACL::STask::Exceptions::VolumeQuotaMappingFailed; use NACL::Expectation; use Data::Dumper; use FG::RAL::util; use constant DEFAULT_TIMEOUT => 900; use constant POLL_DELTA => 1; use constant UGT_CHECKER_DEFAULT_TIMEOUT => 120; use Class::MethodMaker [ scalar => [ { -type => 'NACL::C::Job' }, 'job_component' ], scalar => [ { -default => 0 }, 'use_scrub' ], ]; =head1 NAME NACL::STask::VolumeQuota =head1 CLEANUP METHODS Cleanup can be registered for the following methods Cleanup methods are, VolumeQuota Method Cleanup Method ------------------------------------ on off =head1 DESCRIPTION C provides a number of well-defined but potentially complex or multi-step methods related to Quotas in ONTAP. =head1 ATTRIBUTES =head2 command_interface (Required) As C. A component object that represents the host to which to send commands. =head2 volume (Required) As C. The name of the volume. =head2 vserver (Required for C-mode, ignored for 7-mode) As C. The name of the vserver containing the volume =head2 job_component (Optional) A job component (NACL::C::Job) representing the on/off operation performed on Quotas. =head1 METHODS =head2 on NACL::STask::VolumeQuota->on( command_interface => $command_interface, 'volume' => $volume, vserver => $vserver, nacltask_wait => $boolean, #default '1' nacltask_to_cleanup => 1, # default 0, nacltask_cleanup_manager => $CleanupObj, %other_options ); $quota_obj->on( %options ); (Class method or instance method) start a Volume Quota on Operation. This method provides additional services beyond what's inherent to the product's volume quota on command, as controlled and described by the new C options. =over =item Options =over =item C<< nacltask_wait => 0|1 >> (Optional) If 1 (the default), wait for volume quota on job completion (calls wait_for_on method). If 0, do not wait. =item C<< wait_for_mapping => 0|1 >> (Optional, default to 0 (do not wait for mapping)) Quota mapping is the process to convert a unix UID/GID to a windows SID and vice-versa. The result of this process is to ensure the quota accounting for both of these mapped users is combined. When wait_for_mapping is set to 1, this function will wait for quota scanners to finish mapping. If 0, do not wait. =back =item command_interface, apiset_must, apiset_should, method-timeout, volume, vserver,foreground. =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. All of the other various options to L<< NACL::C::VolumeQuota->on | lib-NACL-C-VolumeQuota-pm/on >> =back =cut sub on { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { nacltask_wait => { type => BOOLEAN, default => 1 }, wait_for_mapping => { type => BOOLEAN, default => 0 }, $pkg->_cleanup_validate_spec(), _optional_scalars(qw (foreground)) }, allow_extra => 1, ); my (%opts_for_cleanup, %opts_for_register, $nacltask_to_cleanup ); $pkg->_move_common_cleanup_opts( source => \%opts, target => \%opts_for_cleanup, ); my $command_interface = $opts{command_interface}; my $wait = delete $opts{nacltask_wait}; my $wait_for_mapping = delete $opts{wait_for_mapping}; my $job_ref; $opts{job_component} ||= \$job_ref; # Call the component VolumeQuota on method $pkg->SUPER::on(%opts); if( ref $pkg_or_obj) { if ( ${ $opts{job_component} } ) { $pkg_or_obj->job_component( ${ $opts{job_component} } ); } } $opts{job_component} = ${ $opts{job_component} }; if ($wait) { my %opts_for_wait = %opts; delete $opts_for_wait{foreground}; $pkg->wait_for_on( %opts_for_wait, wait_for_mapping => $wait_for_mapping, ); } # Register this resource with the cleanup manager for cleanup. if (defined $command_interface->is_7mode()) { delete $opts{job_component}; } $pkg->_copy_common_opts_for_cleanup( 'source' => {%opts, %opts_for_cleanup}, 'target' => \%opts_for_register, 'nacltask_to_cleanup'=> \$nacltask_to_cleanup, 'to_cleanup' => 'off', ); $pkg->_register_for_cleanup(%opts_for_register) if ($nacltask_to_cleanup); $Log->exit() if $may_exit; } ## end sub on =head2 resize NACL::STask::VolumeQuota->resize( command_interface => $command_interface, 'volume' => $volume, vserver => $vserver, nacltask_wait => $boolean, #default '1' %other_options ); $quota_obj->resize( %options ); (Class method or instance method) start a Volume Quota resize Operation. This method provides additional services beyond what's inherent to the product's volume quota resize command, as controlled and described by the new C options. =over =item Options =over =item C<< nacltask_wait => 0|1 >> (Optional) If 1 (the default), wait for volume quota resize job completion (calls wait_for_on method). If 0, do not wait. =item C<< wait_for_mapping => 0|1 >> (Optional, default to 0 (do not wait for mapping)) Quota mapping is the process to convert a unix UID/GID to a windows SID and vice-versa. The result of this process is to ensure the quota accounting for both of these mapped users is combined. When wait_for_mapping is set to 1, this function will wait for quota scanners to finish mapping. If 0, do not wait. =back =item command_interface, apiset_must, apiset_should, method-timeout, volume, vserver,foreground. All of the other various options to L<< NACL::C::VolumeQuota->resize | lib-NACL-C-VolumeQuota-pm/resize >> =back =cut sub resize { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { nacltask_wait => { type => BOOLEAN, default => 1 }, wait_for_mapping => { type => BOOLEAN, default => 0 }, _optional_scalars(qw (foreground)) }, allow_extra => 1, ); my $command_interface = $opts{command_interface}; my $wait = delete $opts{nacltask_wait}; my $wait_for_mapping = delete $opts{wait_for_mapping}; my $job_ref; $opts{job_component} ||= \$job_ref; # Call the component VolumeQuota resize method $pkg->SUPER::resize(%opts); if( ref $pkg_or_obj) { if ( ${ $opts{job_component} } ) { $pkg_or_obj->job_component( ${ $opts{job_component} } ); } } $opts{job_component} = ${ $opts{job_component} }; if ($wait) { my %opts_for_wait = %opts; delete $opts_for_wait{foreground}; # Don't want to use SNMP when waiting for resize, since SNMP doesn't # expose the 'resizing' state. $opts_for_wait{apiset_must} //= {}; $opts_for_wait{apiset_must}->{interface} //= 'CLI|ZAPI'; $pkg->wait_for_on( %opts_for_wait, wait_for_mapping => $wait_for_mapping, ); } $Log->exit() if $may_exit; } ## end sub resize =head2 off NACL::STask::VolumeQuota->off( command_interface => $command_interface, 'volume' => $volume, vserver => $vserver, nacltask_wait => $boolean, #default '1' %other_options ); $quota_obj->off( %options ); (Class method or instance method) start a Volume Quota off Operation. This method provides additional services beyond what's inherent to the product's volume quota off command, as controlled and described by the new C options. =over =item Options =over =item C<< nacltask_wait => 0|1 >> (Optional) If 1 (the default), wait for volume quota off job completion (calls wait_for_off method). If 0, do not wait. =back =item command_interface, apiset_must, apiset_should, method-timeout, volume, vserver,foreground. All of the other various options to L<< NACL::C::VolumeQuota->off | lib-NACL-C-VolumeQuota-pm/off >> =back =cut sub off { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { nacltask_wait => { type => BOOLEAN, default => 1 }, _optional_scalars(qw (foreground)) }, allow_extra => 1, ); my $command_interface = $opts{command_interface}; my $wait = delete $opts{nacltask_wait}; my $job_ref; $opts{job_component} ||= \$job_ref; # Call the component VolumeQuota off method $pkg->SUPER::off(%opts); if ( ref $pkg_or_obj ) { if ( ${ $opts{job_component} } ) { $pkg_or_obj->job_component( ${ $opts{job_component} } ); } } $opts{job_component} = ${ $opts{job_component} }; if ($wait) { my %opts_for_wait = %opts; delete $opts_for_wait{foreground}; $pkg->wait_for_off(%opts_for_wait); } $Log->exit() if $may_exit; } ## end sub off =head2 wait_for_on, wait_for_off #wait_for_on NACL::STask::VolumeQuota->wait_for_on ( command_interface => $ci, volume => $vol_name, vserver => $vs_name, job_component => $last_job, polling_interval => $interval, method-timeout => $timeout, %other_options ); # wait_for_off NACL::STask::VolumeQuota->wait_for_off ( command_interface => $ci, volume => $vol_name, vserver => $vs_name, job_component => $last_job, polling_interval => $interval, method-timeout => $timeout, %other_options ); or $obj->wait_for_on(...); # or $obj->wait_for_off(...); (Class or instance method) Wait for the volume quota on/off to complete. If a job for volume quota on/off is known (because the mode is C-mode and NACL::STask::VolumeQuota->on/off was used, or because it was passed in using the "job" option) then this waits for the job. =over =item Options =over =item C<< volume => $vol_name >> (Required for class method, Not Applicable for instance method) The name of the volume on which the method waits for the volume quota on/off operation to be completed. =item C<< vserver => $vs_name >> (Required for class method, Not Applicable for instance method) (Required for c-mode, ignored for 7-mode) The name of the vserver containing the volume. =item C<< job_component => $last_job >> ( Optional ) This is a reference to job component of the quota on/off operation NACL::STask::VolumeQuota->on/off(..., nacltask_wait => 0, job_component => \$last_job); =item C<< polling_interval => $interval >> (Optional) The interval after which the node will be polled to verify if the quota on/off operation is complete. Defaults to 1 seconds. =item C<< method-timeout => $timeout >> (Optional) The time period after which method reports error if the quota on/off operation does not succeed on the specific volume. Defaults to 500 seconds. =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< apiset_must => $ruleset >> (Optional)See L =item C<< apiset_should => $ruleset >> (Optional)See L =item C<< wait_for_mapping => 0|1 >> (Optional, default to 0 (do not wait for mapping)) Quota mapping is the process to convert a unix UID/GID to a windows SID and vice-versa. The result of this process is to ensure the quota accounting for both of these mapped users is combined. When wait_for_mapping is set to 1, this function will wait for quota scanners to finish mapping. If 0, do not wait. This applies to only quota on operation. =back =back =cut sub wait_for_on { wait_for_status( @_, till_value => "on" ); } sub wait_for_off { wait_for_status( @_, till_value => "off" ); } sub wait_for_status { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref $pkg_or_obj || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { job_component => { type => OBJECT | UNDEF, optional => 1 }, wait_for_mapping => { type => BOOLEAN, default => 0 }, }, allow_extra => 1, ); $opts{job_component} //= $pkg_or_obj->job_component(); $opts{'method-timeout'} ||= DEFAULT_TIMEOUT; $opts{polling_interval} ||= POLL_DELTA; my $wait_for_mapping_flag = delete $opts{wait_for_mapping}; # Call the wait_for_completion from the Component my $intermediate_state; if ( $opts{till_value} eq 'on' ) { $intermediate_state = qr(^off); } elsif ( $opts{till_value} eq 'off' ) { $intermediate_state = qr(^on); } $pkg->wait_for_completion( %opts, attribute_to_check => 'state', valid_intermediate_states => [ qr(^initializing), qr(^resizing), $intermediate_state, qr(^mixed) ], ); if($wait_for_mapping_flag){ $pkg->wait_for_mapping(%opts); } $Log->exit() if $may_exit; } ## end sub wait_for_status =head2 wait_for_mapping NACL::STask::VolumeQuota->wait_for_mapping ( command_interface => $ci, volume => $vol_name, vserver => $vs_name, polling_interval => $interval, method-timeout => $timeout, %other_options ); or $obj->wait_for_mapping(...); (Class or instance method) Wait for quota user mapping process to complete. The quota mapping is the process to convert a unix UID/GID to a windows SID and vice-versa. The result of this process is to ensure the quota accounting for both of these mapped users is combined. =over =item Options =over =item C<< volume => $vol_name >> (Required for class method, Not Applicable for instance method) The name of the volume on which the method waits for the volume quota mapping operation to be completed. =item C<< vserver => $vs_name >> (Required for class method, Not Applicable for instance method) The name of the vserver containing the volume. =item C<< polling_interval => $interval >> (Optional) The interval after which the node will be polled to verify if the quota mapping operation is complete. Defaults to 1 second. =item C<< method-timeout => $timeout >> (Optional) The time period after which method reports error if the quota mapping operation does not complete for the specific volume. Defaults to 1200 seconds. =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =back =back =cut sub wait_for_mapping { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref $pkg_or_obj || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, allow_extra => 1, ); $opts{'method-timeout'} ||= DEFAULT_TIMEOUT; $opts{polling_interval} ||= POLL_DELTA; my $command_interface = $opts{command_interface}; my $vserver = $opts{vserver}; my $vol_name = $opts{volume}; my $vol_obj = NACL::STask::Volume->new( command_interface => $command_interface, vserver => $vserver, volume => $vol_name, ); my $is_flexgroup = $vol_obj->state()->is_flexgroup(); my $instance_name = $vol_name; if( $is_flexgroup eq 'true' ) { #FlexGroup $instance_name .= '__*/*'; } else { #FlexVol $instance_name .= '/*'; } my $endtime = time() + $opts{'method-timeout'}; while ( time() < $endtime ) { # Setting apiset to use CLI since this is diag level info # available only via CLI. my @stats_list = NACL::CS::Statistics->fetch( command_interface => $command_interface, filter => { vserver => $vserver, object => 'quota', instance => $instance_name, counter => 'quota_records', raw => 'true', }, apiset_must => { interface => "CLI", }, ); my $done = 1; foreach my $stats (@stats_list) { my @labels = @{$stats->labels()}; my @values = @{$stats->values()}; my %hash; @hash{@labels} = @values; if($hash{'MAPPED'} == 0) { $Log->comment("Mapping is done for Instance: " . $stats->instance()); $Log->comment("$_ -> $hash{$_}\n") for (keys %hash); } else { $Log->comment("Mapping is not done for Instance: " . $stats->instance()); $done = 0; $Log->debug("Quota record statistics: " . Dumper(%hash)); last; } } if($done) { $Log->exit() if $may_exit; return; # Quota code finished working on name mapping. RETURN here } # Sleep for polling interval Tharn::sleep($opts{polling_interval}); } # Statistics indicates mapping is not complete yet. my $ex_text = "Quota name mapping did not complete in time."; NACL::STask::Exceptions::VolumeQuotaMappingFailed->throw($ex_text); # NOT REACHED $Log->exit() if $may_exit; } ## end sub wait_for_mapping =head2 wait_for_check_ugt NACL::STask::VolumeQuota->wait_for_check_ugt ( command_interface => $ci, volume => $vol_name, vserver => $vs_name, polling_interval => $interval, method-timeout => $timeout, %other_options ); or $obj->wait_for_check_ugt(...); (Class or instance method) Wait for volume quota user, group and tree (UGT) checker to start and reach a target value. The UGT checker will store hints whether quotas for each type are present. When a quota type is known to be absent, quota checks and updates for that type will not occur. =over =item Options =over =item C<< volume => $vol_name >> (Required for class method, Not Applicable for instance method) The name of the volume on which the method waits for the volume quota user, group and tree checker to be completed. =item C<< vserver => $vs_name >> (Required for class method, Not Applicable for instance method) The name of the vserver containing the volume. =item C<< polling_interval => $interval >> (Optional) The interval after which the node will be polled to verify if the quota user, group and tree checker is complete. Defaults to 1 second. =item C<< method-timeout => $timeout >> (Optional) The time period after which method reports error if the quota user, group and tree check does not complete for the specific volume. Defaults to 120 seconds. =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< target_value => 0|1 >> (Optional, default to 1. Wait for check user, group and tree (UGT) to reach the target value. =back =back =cut sub wait_for_check_ugt { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref $pkg_or_obj || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { target_value => { type => SCALAR, default => 1, }, }, allow_extra => 1, ); $opts{'method-timeout'} ||= UGT_CHECKER_DEFAULT_TIMEOUT; $opts{polling_interval} ||= POLL_DELTA; my $target_value = $opts{target_value}; my $endtime = time() + $opts{'method-timeout'}; while ( time() < $endtime ) { # Setting apiset to use CLI since this is diag level info # available only via CLI. my $scan_state = NACL::CS::Statistics->fetch( command_interface => $opts{command_interface}, filter => { vserver => $opts{vserver}, object => 'quota', instance => $opts{volume} . '*', counter => 'quota_db_ugt_scan_state', raw => 'true', }, apiset_must => { interface => "CLI", }, ); # The check must have started too. my $scan_blocks = $pkg->get_quotadb_ugt_scan_blocks(%opts); if ($scan_blocks > 0 && $scan_state->value() eq $target_value) { $Log->exit() if $may_exit; return; } # Sleep for polling interval Tharn::sleep($opts{polling_interval}); } my $ex_text = "Quota user, group and tree check did not complete in time."; NACL::STask::Exceptions::VolumeQuotaMappingFailed->throw($ex_text); # NOT REACHED $Log->exit() if $may_exit; } ## end sub wait_for_check_ugt =head2 verify_report NACL::STask::VolumeQuota->verify_report( command_interface => $command_interface, filter => { %filter }, verify_fields => ( %fields }, %other_options ); $quota_obj->verify_report( %options ); (Class method or instance method) This method is like CS-like method which accepts some of the parameters of L (filter, allow_empty) This method fetches quota report and verifies the quota report is as expected. For negative verification: - This method will throw NoElementFount exception if no matching elements are found. - This will throw CR:Setup::Exception::ExpectationNotMetError if the value does not match =over =item Options =over =item C<< filter => HASHREF >> (Required) This is used to filter the Quota report. This is component state style filter option. =item C<< verify_fields => HASHREF >> (Required) This is hash of fields and value to compare. =item C<< allow_multi_record => 0 >> (Optional) Default is 0. This option will if indicate if the filter will return single Quota Report Record or multiple records. =item C<< use_scrub => 1 >> (Optional) Default is 0. This option indicates whether the scrub should be used to flush the remote cached data before a quota report for a FlexGroup. It is for FlexGroup only and will be ignored for FlexVol. =back =cut sub verify_report { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { filter => { type => HASHREF }, verify_fields => { type => HASHREF }, allow_multi_record => { type => BOOLEAN, default => 0 }, allow_empty => { type => BOOLEAN, default => 0 }, use_scrub => { type => BOOLEAN, optional => 1 }, }, allow_extra => 1, ); # if this is an object, take use_scrub from the object (defaulting to 0) my $use_scrub = ( ref $pkg_or_obj ? $pkg_or_obj->use_scrub() : 0 ); # override use_scrub if method argument provided $use_scrub = ( exists $opts{use_scrub} ? $opts{use_scrub} : $use_scrub ); my %common_opts; $pkg_or_obj->_copy_common_component_params( source => \%opts, target => \%common_opts ); my $command_interface = $opts{command_interface}; my $vserver = $opts{vserver}; my $vol_name = $opts{volume}; if( $use_scrub ) { my $vol_obj = NACL::STask::Volume->new( command_interface => $command_interface, vserver => $vserver, volume => $vol_name, ); if( $vol_obj->state()->is_flexgroup() eq 'true' ) { my @constituents = $vol_obj->get_flexgroup_constituents( apiset_should => { interface => 'CLI' }, # for speed ); $pkg_or_obj->_wait_for_remote_scrub( %common_opts, command_interface => $opts{command_interface}, vserver => $opts{vserver}, volume => $opts{volume}, flexgroup_constituents => \@constituents, ); } } #Get Report Fetch Options for CLI or ZAPI my $fetch_opts = _get_fetch_opts( command_interface => $command_interface, volume => $vol_name, vserver => $vserver, filter => $opts{filter}, allow_empty => $opts{allow_empty}, common_opts => \%common_opts, ); # Fetch Quota Report my @quota_report = NACL::CS::VolumeQuotaReport->fetch( %{$fetch_opts}, ); # How many records return by the Quota report query? Verify against # what user was expecting - Single vs multi records. if( (scalar(@quota_report) > 1) && (!$opts{allow_multi_record}) ) { my $ex_text = "Quota report returned " . scalar(@quota_report) . " records when expecting only 1 record. Please verify filter."; $Log->comment($ex_text); $Log->comment("Dumping Quota Report: " . Dumper(@quota_report)); NACL::STask::Exceptions::VolumeQuotaReportVerify->throw($ex_text); } # TODO: Else multiple records process. # We will implement the mutli record check when need arise # Time to do real work! Let's compare quota report with what user was # expecting. # First, translate the verify fields for CLI or ZAPI. my $fields_out = _translate_verify_fields( verify_fields => $opts{verify_fields}, apiset_interface => $fetch_opts->{apiset_must}->{interface}, ); foreach my $quota_report_record (@quota_report) { foreach my $field (keys %{$fields_out}) { # This will throw CR:Setup::Exception::ExpectationNotMetError # if the value does not match $Log->comment("Verifying value of field: $field"); if( ($field eq 'quota_specifier') && ($fields_out->{$field} eq '"*"|""') ) { #quota_specifier could be either "*" or "" in ZAPI. #See burt416416 and burt911359 for more info. if( ($quota_report_record->$field ne '"*"') && ($quota_report_record->$field ne '""') ) { NATE::BaseException->throw("quota_specifier does not match: expected \"\*\" or \"\"; but, got " . $quota_report_record->$field); } $Log->comment("matched\n\n"); } else { this($quota_report_record->$field)->should(Be->lexically_equal_to( $fields_out->{$field})); $Log->comment("matched\n\n"); } } } $Log->exit() if $may_exit; } ## end sub verify_report #Get Quota Report Fetch Options for CLI or ZAPI. sub _get_fetch_opts { my %args = @_; my $command_interface = $args{command_interface}; my $volume = $args{volume}; my $vserver = $args{vserver}; my $filter = $args{filter}; my $allow_empty = $args{allow_empty}; my $common_opts = $args{common_opts}; #Randomly select CLI or ZAPI first my $apiset_interface = int(rand(2)) ? 'CLI' : 'ZAPI'; my %fetch_opts = %{$common_opts}; $fetch_opts{command_interface} = $command_interface; my $quota_singleton = NACL::Util::QuotaLibSingleton->get(); my $apiset_must = $quota_singleton->apiset_must(%fetch_opts); my $apiset_should = $quota_singleton->apiset_should(%fetch_opts); if ( defined($apiset_must->{interface}) ) { $apiset_interface = $apiset_must->{interface}; $Log->comment("The apiset_must rulesets in effect say we must " . "use the $apiset_interface apiset interface"); } elsif ( defined($apiset_should->{interface}) ) { $apiset_interface = $apiset_should->{interface}; $Log->comment("The apiset_should rulesets in effect say we should " . "use the $apiset_interface apiset interface"); } $apiset_must->{interface} //= $apiset_interface; if ( $apiset_interface eq 'SNMP' ) { # Field values are compatible between CLI and SNMP. However, there # are a few fields SNMP doesn't support. For that reason, we need to # allow call_on_apiset to fall back to CLI. $apiset_must->{interface} = 'CLI|SNMP'; } $Log->comment("Using $apiset_interface to get the quota report"); #Translate a filter for CLI or ZAPI. my $fetch_filter = _translate_filter( filter => $filter, apiset_interface => $apiset_interface, volume => $volume, ); $fetch_filter->{volume} = $volume; $fetch_filter->{vserver} = $vserver; $fetch_opts{allow_empty} = $allow_empty; $fetch_opts{apiset_must} = $apiset_must; $fetch_opts{filter} = $fetch_filter; return \%fetch_opts; } #Translate a filter for CLI or ZAPI. #The input filter uses the CLI format as a standard. sub _translate_filter { my %args = @_; my $filter = $args{filter}; my $apiset_interface = $args{apiset_interface}; my $volume = $args{volume}; my %filter_out = %{$filter}; if( ($apiset_interface eq 'ZAPI') && ($filter_out{'quota-target'}) ) { #For CLI, quota-target is a string of user names separated by ','. #For ZAPI, quota-target is actually quota-users or an array of users. $Log->comment("Convert quota-target " . $filter_out{'quota-target'} . " to quota-users for ZAPI"); my @users = split /,/, $filter_out{'quota-target'}; $filter_out{'quota-users'} = []; foreach my $user (@users) { my $key = 'quota-user-name'; if( $user =~ /^\d+$/ ) { #This is a User ID instead of a User Name. $key = 'quota-user-id'; } push $filter_out{'quota-users'}, { $key => $user, }; } delete $filter_out{'quota-target'}; } #For CLI, the size unit is Byte. #For ZAPI, the size unit is KB. if( $apiset_interface eq 'ZAPI' ) { foreach my $size_field ('disk-used', 'disk-limit', 'threshold', 'soft-disk-limit') { if( $filter_out{$size_field} ) { if (Scalar::Util::looks_like_number( $filter_out{$size_field} )) { my $orig_val = $filter_out{$size_field}; $filter_out{$size_field} /= 1024; $Log->comment("Modify the value of the filter $size_field from " . $orig_val . " to " . $filter_out{$size_field} . " for ZAPI"); } } } } if( ($apiset_interface eq 'ZAPI') && ($filter_out{'quota-specifier'}) ) { #For ZAPI, quota-specifier could be either "*" or "". #See burt416416 and burt911359 for more info. if( ($filter_out{'quota-specifier'} eq '"*"') || ($filter_out{'quota-specifier'} eq '*') ) { $Log->comment("Modify the value of the filter quota-specifier from " . $filter_out{'quota-specifier'} . " to \"*\"\|\"\" for ZAPI"); $filter_out{'quota-specifier'} = '"*"|""'; } elsif( ($filter_out{'quota-type'} && ($filter_out{'quota-type'} eq 'tree')) || ($filter_out{'tree'} && ($filter_out{'tree'} eq $filter_out{'quota-specifier'})) ) { #For ZAPI, quota-specifier is the full path of a tree here. my $tree_path = '/vol/' . $volume . '/' . $filter_out{'quota-specifier'}; $Log->comment("Modify the value of the filter quota-specifier from " . $filter_out{'quota-specifier'} . " to $tree_path for ZAPI"); $filter_out{'quota-specifier'} = $tree_path; } } return \%filter_out; } #Translate the verify fields for CLI or ZAPI. #The input verify fields use the CLI format as a standard. sub _translate_verify_fields { my %args = @_; my $verify_fields = $args{verify_fields}; my $apiset_interface = $args{apiset_interface}; my %fields_out = %{$verify_fields}; #Use the normalized string of quota target (i.e., quota_target_str) for both CLI and ZAPI. foreach my $key ('quota_target', 'quota-target') { if( $fields_out{$key} ) { $fields_out{quota_target_str} = $fields_out{$key}; delete $fields_out{$key}; } } #For ZAPI, quota specifier could be either "*" or "". if( $apiset_interface eq 'ZAPI' ) { foreach my $key ('quota_specifier', 'quota-specifier') { if( $fields_out{$key} ) { if( ($fields_out{$key} eq '"*"') || ($fields_out{$key} eq '*') ) { $Log->comment("Modify the value of the field $key from " . $fields_out{$key} . " to \"*\"\|\"\" for ZAPI"); $fields_out{$key} = '"*"|""'; } } } } # KByte to Byte conversion done automatically in NACL::CS::VolumeQuotaReport return \%fields_out; } =back =head2 report_expected $quota_obj->report_expected( { 'quota-target' => $unix_user_name, 'soft-file-limit' => $default_soft_file_limit, 'files-used' => $total_file_count, }, { 'quota-target' => $cifs_user_name, 'soft-file-limit' => $default_soft_file_limit, 'files-used' => $total_file_count, }, ); (Instance method) This method is like CS-like method which takes an array of hash ref (filters) and verifies quota report for each filter. This method fetches quota report and verifies it returns exactly one row for each filter hash. For negative verification: - This method will throw NoElementFount exception if no matching elements are found for any of the filter hash. - This method will throw NACL::STask::Exceptions::VolumeQuotaReportVerify if any filter hash returns multiple quota report row. =over =item Options =over =back =cut sub report_expected { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => [], additional_spec => { }, allow_extra => 1, ); # if this is an object, take use_scrub cue from the object (default to 0) my $use_scrub = ( ref $pkg_or_obj ? $pkg_or_obj->use_scrub() : 0 ); # NOTE: method arguments (unstructured array) don't permit override my %common_opts; $pkg_or_obj->_copy_common_component_params( source => \%opts, target => \%common_opts ); if ( $use_scrub ) { my $vol_obj = NACL::STask::Volume->new( command_interface => $opts{command_interface}, vserver => $opts{vserver}, volume => $opts{volume}, ); if( $vol_obj->state()->is_flexgroup() eq 'true' ) { my @constituents = $vol_obj->get_flexgroup_constituents( apiset_should => { interface => 'CLI' }, # for speed ); $pkg_or_obj->_wait_for_remote_scrub( %common_opts, command_interface => $opts{command_interface}, vserver => $opts{vserver}, volume => $opts{volume}, flexgroup_constituents => \@constituents, ); } } # Iterate over filter list and verify volume quota report returns # exactly one row for each filter foreach my $filter (@args) { #Get Report Fetch Options for CLI or ZAPI my $fetch_opts = _get_fetch_opts( command_interface => $opts{command_interface}, volume => $opts{volume}, vserver => $opts{vserver}, filter => $filter, allow_empty => 1, common_opts => \%common_opts, ); # Fetch Quota Report my @quota_report = NACL::CS::VolumeQuotaReport->fetch( %{$fetch_opts}, ); # How many records return by the Quota report query? The expectation # here is exactly one. # If returned 0 record if( (scalar(@quota_report) == 0) ) { my $ex_text = "Quota report did not return any record with filter." . " Please verify filter."; $Log->comment($ex_text); $Log->comment("Fetch report without filter for debugging:"); NACL::CS::VolumeQuotaReport->fetch( command_interface => $opts{command_interface}, %common_opts, filter => { volume => $opts{volume}, vserver => $opts{vserver}, }, allow_empty => 1, ); NACL::STask::Exceptions::VolumeQuotaReportVerify->throw($ex_text); } # If returned more than 1 records if( (scalar(@quota_report) > 1) ) { my $ex_text = "Quota report returned " . scalar(@quota_report) . " records when expecting only 1 record. Please verify filter."; $Log->comment($ex_text); $Log->comment("Dumping Quota Report: " . Dumper(@quota_report)); NACL::STask::Exceptions::VolumeQuotaReportVerify->throw($ex_text); } } $Log->exit() if $may_exit; } ## end sub report_expected =back =head2 report_bulk_verify $quota_obj->report_bulk_verify( filters => [ { 'quota-target' => $unix_user_name, 'disk-used' => $total_used_disk_size, 'files-used' => $total_file_count, }, { 'quota-target' => $cifs_user_name, 'disk-used' => $total_used_disk_size, 'files-used' => $total_file_count, }, ... ], ); (Instance method) This method takes an array of hash ref (filters) and verifies the quota report for each filter. This method fetches the entire quota report without filters only once and verifies it returns exactly one row for each filter hash. For negative verification: - This method will throw NoElementFount exception if no matching elements are found for any of the filter hash. - This method will throw NACL::STask::Exceptions::VolumeQuotaReportVerify if any filter hash returns multiple quota report row. =over =item Options =over =back =cut sub report_bulk_verify { $Log->enter() if $may_enter; my ($pkg_or_obj, %args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => [], additional_spec => { }, allow_extra => 1, ); my $filters = exists $args{filters} ? $args{filters} : []; # if this is an object, take use_scrub from the object (defaulting to 0) my $use_scrub = ( ref $pkg_or_obj ? $pkg_or_obj->use_scrub() : 0 ); # override use_scrub if method argument provided $use_scrub = ( exists $args{use_scrub} ? $args{use_scrub} : $use_scrub ); my %common_opts; $pkg_or_obj->_copy_common_component_params( source => \%opts, target => \%common_opts ); if( $use_scrub ) { my $vol_obj = NACL::STask::Volume->new( command_interface => $opts{command_interface}, vserver => $opts{vserver}, volume => $opts{volume}, ); if( $vol_obj->state()->is_flexgroup() eq 'true' ) { my @constituents = $vol_obj->get_flexgroup_constituents( apiset_should => { interface => 'CLI' }, # for speed ); $pkg_or_obj->_wait_for_remote_scrub( %common_opts, command_interface => $opts{command_interface}, vserver => $opts{vserver}, volume => $opts{volume}, flexgroup_constituents => \@constituents, ); } } #Randomly select CLI or ZAPI first my $apiset_interface = int(rand(2)) ? 'CLI' : 'ZAPI'; my $quota_singleton = NACL::Util::QuotaLibSingleton->get(); my $apiset_should = $quota_singleton->apiset_should(%opts); if ( defined($apiset_should->{interface}) ) { $apiset_interface = $apiset_should->{interface}; $Log->comment("The apiset_should rulesets in effect say we should " . "use the $apiset_interface apiset interface"); } my $apiset_must = { interface => $apiset_interface }; if ( $apiset_interface eq 'SNMP' ) { # Field values are compatible between CLI and SNMP. However, there # are a few fields SNMP doesn't support. For that reason, we need to # allow call_on_apiset to fall back to CLI. $apiset_must->{interface} = 'CLI|SNMP'; } $Log->comment("Using $apiset_interface to get the quota report"); # Fetch Quota Report without filters only once my @quota_report = NACL::CS::VolumeQuotaReport->fetch( command_interface => $opts{command_interface}, %common_opts, filter => { volume => $opts{volume}, vserver => $opts{vserver}, }, apiset_must => $apiset_must, ); # Iterate over filter list and verify volume quota report returns # exactly one row for each filter foreach my $filter (@{$filters}) { my $filter_out = _translate_verify_fields( verify_fields => $filter, apiset_interface => $apiset_interface, ); my @matches = NACL::CS::ComponentState->get_matching_objects( state_objs => \@quota_report, filter => $filter_out, ); if( scalar(@matches) == 0 ) { my $ex_text = "Quota report did not return any record with filter " . Dumper($filter); $Log->comment($ex_text); NACL::STask::Exceptions::VolumeQuotaReportVerify->throw($ex_text); } if( scalar(@matches) > 1 ) { my $ex_text = "Quota report returned " . scalar(@matches) . " records with filter " . Dumper($filter) . " when expecting only 1 record."; $Log->comment($ex_text); NACL::STask::Exceptions::VolumeQuotaReportVerify->throw($ex_text); } } $Log->exit() if $may_exit; } ## end sub report_bulk_verify =back =head2 get_quotadb_blocks_num NACL::STask::VolumeQuota->get_quotadb_blocks_num ( command_interface => $ci, volume => $vol_name, %other_options ); or $obj->get_quotadb_blocks_num(...); (Class or instance method) get the number of blocks consumed by the Quota database.. =over =item Options =over =item C<< volume => $vol_name >> (Required for class method, Not Applicable for instance method) The name of the volume where the quota rules are applied. =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =back =cut sub get_quotadb_blocks_num { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref $pkg_or_obj || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, allow_extra => 1, ); my $blknum = NACL::CS::Statistics->fetch( command_interface => $opts{command_interface}, filter => { vserver => $opts{vserver}, object => 'quota', instance => $opts{volume} . '*', counter => 'quota_db_blocks', raw => 'true', }, apiset_must => { interface => "CLI", }, ); $Log->comment('Number of blocks consumed by the quotadatabase = ' . $blknum->value()); $Log->exit() if $may_exit; return $blknum->value(); } ## get_quotadb_blocks_num =back =head2 get_quotadb_ugt_scan_blocks NACL::STask::VolumeQuota->get_quotadb_ugt_scan_blocks ( command_interface => $ci, volume => $vol_name, %other_options ); or $obj->get_quotadb_ugt_scan_blocks(...); (Class or instance method) get the number of blocks in the Quota database the user, group and tree check has examined. =over =item Options =over =item C<< volume => $vol_name >> (Required for class method, Not Applicable for instance method) The name of the volume where the quota rules are applied. =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =back =cut sub get_quotadb_ugt_scan_blocks { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref $pkg_or_obj || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, allow_extra => 1, ); my $blknum = NACL::CS::Statistics->fetch( command_interface => $opts{command_interface}, filter => { vserver => $opts{vserver}, object => 'quota', instance => $opts{volume} . '*', counter => 'quota_db_ugt_scan_blocks', raw => 'true', }, apiset_must => { interface => "CLI", }, ); $Log->exit() if $may_exit; return $blknum->value(); } ## get_quotadb_ugt_scan_blocks 1;