# # 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 ComponentState Module ## @author Dheeraj.K@netapp.com, pragyan@netapp.com, dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::CS::VolumeQuota =head1 DESCRIPTION C is a derived class of L. It represents the state of VolumeQuota in an ONTAP VolumeQuota. A related class is L, which represents access to an ONTAP VolumeQuota. =head1 ATTRIBUTES The individual pieces of data that are part of the state of the VolumeQuota element are the attributes of the VolumeQuota ComponentState. =over =item C<< vserver >> Name of the vserver in which the volume resides Filled in for CMode CLI/ZAPI/SNMP. =item C<< volume >> Name of the volume to which the quota state applies Filled in for CMode CLI/ZAPI/SNMP, 7Mode CLI/ZAPI. =item C<< "state" >> Quota state for the volume. Filled in for CMode CLI/ZAPI/SNMP, 7Mode CLI/ZAPI. CMode SNMP has a limitation where this will show as C in cases where CMode CLI/ZAPI would show C. =item C<< "scan_status" >> Percentage of the files in the volume that have been scanned by quota initialization. Filled in for CMode CLI/ZAPI/SNMP, 7Mode ZAPI. =item C<< "last_error" >> Most recently generated error message from the last quota operation. Filled in for CMode CLI/ZAPI. =item C<< "sub_status" >> Additional quota status for the volume. Filled in for CMode CLI/ZAPI, 7Mode ZAPI. =item C<< "instance" >> Filled in for CMode CLI. =item C<< "quota_status" >> Filled in for CMode ZAPI, 7Mode ZAPI. =item C<< errors >> Filled in for CMode CLI. =item C<< is_user_quota_enforced >> Filled in for CMode CLI. =item C<< sub_status_7g >> Filled in for CMode CLI. =item C<< logging_interval >> Filled in for CMode CLI. =item C<< is_tree_quota_enforced >> Filled in for CMode CLI. =item C<< is_group_quota_enforced >> Filled in for CMode CLI. =item C<< logging >> Filled in for CMode CLI. =item C<< foreground >> Filled in for CMode CLI. =item C<< state_zapi >> Filled in for CMode CLI. =back =cut package NACL::CS::VolumeQuota; use strict; use warnings; use feature 'state'; use Params::Validate qw(validate); use NACL::APISet::Exceptions::ResponseException qw(:try); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use NACL::ComponentUtils qw(Dumper); use NACL::CS::_Mixins::VolumeQuota qw(:all); use NACL::Util::QuotaLibSingleton; use base 'NACL::CS::ComponentState::ONTAP'; use constant { # XXX: NetApp::ZAPIError is the "right" answer, but it keeps # breaking everything. EVOLUMEDOESNOTEXIST => 13040, EVOLUMEOFFLINE => 13042, EVOL_RESTRICTED => 13061, }; use Class::MethodMaker [ # These fields are from the C Mode "volume quota policy show" scalar => 'vserver', scalar => 'volume', scalar => 'state', scalar => 'scan_status', scalar => 'last_error', scalar => 'sub_status', scalar => 'instance', array => 'quota_status', scalar => 'errors', scalar => 'is_user_quota_enforced', scalar => 'sub_status_7g', scalar => 'logging_interval', scalar => 'is_tree_quota_enforced', scalar => 'is_group_quota_enforced', scalar => 'logging', scalar => 'foreground', scalar => 'state_zapi', ]; sub isa { $Log->enter() if $may_enter; my ($pkg_or_obj, $kind) = @_; my $isa = $pkg_or_obj->_build_isa( kind => $kind, alias => 'NACL::CS::Quota' ); $Log->exit() if $may_exit; return $isa; } =head1 METHODS =head2 fetch my $quota_state = NACL::CS::VolumeQuota->fetch(command_interface=>$ci,...); my @quota_states = NACL::CS::VolumeQuota->fetch(command_interface=>$ci,...); see L Supports CMode CLI/ZAPI/SNMP, 7Mode CLI/ZAPI. Invokes C API (when zapi_type => "iter") or C API (when zapi_type => "non-iter") for CMode ZAPI. Walks C for CMode SNMP. Invokes C command for 7Mode CLI. Invokes C API for 7Mode ZAPI. =over =item Exceptions =over =item C When there are no elements matching the query specified or elements of that type doesn't exist, then this exception will be thrown. =back =back =cut sub fetch { $Log->enter() if $may_enter; my ($pkg, %opts) = @_; my $quota_singleton = NACL::Util::QuotaLibSingleton->get(); $opts{apiset_must} = $quota_singleton->apiset_must(%opts); $opts{apiset_should} = $quota_singleton->apiset_should(%opts); my @state_objs = $pkg->SUPER::fetch( %opts, choices => [ { method => '_fetch_cmode_cli', interface => 'CLI', set => 'CMode', zapi_type => 'none', }, { method => '_fetch_7mode_cli', interface => 'CLI', set => '7Mode', zapi_type => 'none', }, { method => '_fetch_7mode_zapi', interface => 'ZAPI', set => '7Mode', zapi_type => 'non-iter', }, { method => '_fetch_cmode_zapi', check => '_fetch_cmode_zapi_check', interface => 'ZAPI', set => 'CMode', zapi_type => 'iter', }, { method => '_fetch_cmode_zapi_non_iter', check => '_fetch_cmode_zapi_non_iter_check', interface => 'ZAPI', set => 'CMode', zapi_type => 'non-iter', }, { method => '_fetch_cmode_snmp', check => '_fetch_cmode_snmp_check', interface => 'SNMP', set => 'CMode', zapi_type => 'none', }, ], show_cmd => 'volume quota show', exception_text => 'No matching volume quota(s) found' ); $Log->exit() if $may_exit; return wantarray ? @state_objs : $state_objs[0]; } ## end sub fetch sub _fetch_cmode_cli { $Log->enter() if $may_enter; my $pkg = shift; my @state_objs = $pkg->SUPER::_fetch_cmode_cli(@_, api => 'volume_quota_show'); $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_cmode_cli sub _fetch_7mode_zapi { $Log->enter() if $may_enter; my $pkg = shift; $Log->debug("Opts to 'fetch' backend are:\n" . Dumper({@_})); my %opts = validate @_, $pkg->_fetch_backend_validate_spec(); my $apiset = $opts{apiset}; my $requested_fields = $opts{requested_fields}; my $filter = $opts{filter}; my %vol_info_args; # The only server-side filtering possible in ZAPI is if the # caller limits to one volume name, then we can get info on just # that volume. Otherwise, we get info on all volumes and # filter out undesired entries somewhere below. if (defined $filter->{volume}) { $vol_info_args{volume} = $filter->{volume}; } # find all the volumes present. my ($volume_response, $caught_exception); try { $Log->debug( "Invoking 'volume_list_info' to find all volumes present"); $volume_response = $apiset->volume_list_info(%vol_info_args); } catch NACL::APISet::Exceptions::ResponseException with { # A caught exception indicates that the volume being looked for # does not exist. We catch the exception and return immediately. The # 'fetch' frontend decides whether to throw a NoElementsFound # exception based on the value of 'allow_empty' $caught_exception = 1; }; if ($caught_exception) { $Log->exit() if $may_exit; return; } my $volume_output = $volume_response->get_parsed_output(); my @volumes; my $volume_info = $volume_output->[0]->{volumes}->[0]->{"volume-info"}; foreach my $vol_info (@$volume_info) { if ($vol_info->{"raid-status"} !~ /snapmirrored/) { push(@volumes, $vol_info->{name}); } } my @state_objs; foreach my $vol (@volumes) { # find the quota status of each volume. $Log->debug( "Invoking 'quota_status' to get quota status of each volume"); my $response = $apiset->quota_status(volume => $vol); my $output = $response->get_parsed_output(); my %_quota_info = (%{$output->[0]}, volume => $vol); $_quota_info{state} = $_quota_info{status}->[1]; my $errors = $_quota_info{'quota-errors'} || ''; $errors =~ /\n(.*)\n$/; $_quota_info{last_error} = $1; my $quota_info = $pkg->_hash_copy( source => \%_quota_info, copy => [qw(volume state last_error)], map => { 'status' => 'quota_status', 'substatus' => 'sub_status', 'percent-complete' => 'scan_status', }, ); $Log->debug("VolumeQuota info:\n" . Dumper($quota_info)); my $obj = $pkg->new(command_interface => $opts{command_interface}); $obj->_set_fields(row => $quota_info); push @state_objs, $obj; } ## end foreach my $vol (@volumes) $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_7mode_zapi sub _fetch_7mode_cli { $Log->enter() if $may_enter; my $pkg = shift; my %opts = validate @_, $pkg->_fetch_backend_validate_spec(); my $apiset = $opts{apiset}; my ($response, $caught_exception); my $cmd_args = $pkg->_hash_copy( source => $opts{filter}, map => {'volume' => 'volume-name'}, ); try { $response = $apiset->quota_status( 'connectrec-timeout' => 1200, %$cmd_args ); } ## end try catch NACL::APISet::Exceptions::InvalidParamValueException with { # A caught exception indicates that the qtree being looked for # does not exist. We catch the exception and return immediately. The # 'fetch' frontend decides whether to throw a NoElementsFound # exception based on the value of 'allow_empty' $caught_exception = 1; }; if ($caught_exception) { $Log->exit() if $may_exit; return; } my $output = $response->get_parsed_output(); my @state_objs; foreach my $row (@$output) { my $final_attributes = $pkg->_hash_copy( source => $row, copy => [qw( volume )], map => {'status' => 'state',}, ); my $obj = $pkg->new(command_interface => $opts{command_interface}); $obj->_set_fields(row => $final_attributes); push @state_objs, $obj; } ## end foreach my $row (@$output) $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_7mode_cli sub _fetch_cmode_zapi { $Log->enter() if $may_enter; my ($pkg, %opts) = @_; my $map = $pkg->_common_zapi_map(); my $copy = $pkg->_common_zapi_copy(); my @state_objs = $pkg->SUPER::_fetch_cmode_zapi( %opts, api => "quota-status-iter", map => $map, copy => $copy, ); $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_cmode_zapi sub _fetch_cmode_zapi_non_iter { $Log->enter() if $may_enter; my $pkg = shift; $Log->debug("Opts to 'fetch' backend are:\n" . Dumper({@_})); my %opts = validate @_, $pkg->_fetch_backend_validate_spec(); my $apiset = $opts{apiset}; my $volume = $opts{filter}->{volume}; my $vserver = $opts{filter}->{vserver}; my %api_opts = (volume => $volume); $vserver = $pkg->_handle_zapi_vserver_context( api_opts => \%api_opts, vserver => $vserver, command_interface => $opts{command_interface}, ); my $response; try { $response = $apiset->quota_status(%api_opts); } catch NACL::APISet::Exceptions::CommandFailedException with { # There are a few errors that get masked by CLI and iter ZAPI, # which simply end up returning no elements. Catch some of those # errors here, for compatibility between interfaces. my $exc = shift; $Log->debug("APISet call threw " . ref($exc)); if (!grep { $exc->error_number() == $_ } (EVOLUMEDOESNOTEXIST, EVOLUMEOFFLINE, EVOL_RESTRICTED)) { $exc->throw(); } $Log->debug("It was an error ignored by CLI and iter ZAPI; " . "returning empty list of elements"); }; if (!defined($response)) { $Log->exit() if $may_exit; return; } my $map = $pkg->_common_zapi_map(); delete $map->{state}; # Needs special handling, collision with ZAPI status $map->{'quota-status'} = ['quota-errors', ]; # Vs quota-error-msgs for iter my $output = $response->get_parsed_output(); my $quota_row = $pkg->_zapi_hash_copy( source => $output->[0], map => [reverse %$map], source_has_extra_arrays => 1, ); $quota_row->{state} = $output->[0]->{status}->[1]; # [0] is ZAPI status $quota_row->{volume} = $volume; $quota_row->{vserver} = $vserver; my $obj = $pkg->new(command_interface => $opts{command_interface}); $obj->_set_fields(row => $quota_row); my %should_be_filled = $pkg->_calculate_fields_to_be_filled_in( %opts, cli_to_zapi_hash => $map, ); foreach my $key (keys %should_be_filled) { if (!$obj->can($key)) { next; } my $isset = "${key}_isset"; if (!$obj->$isset()) { $obj->$key('-'); } } $Log->exit() if $may_exit; return ($obj,); } ## end sub _fetch_cmode_zapi_non_iter sub _fetch_cmode_zapi_check_common { $Log->enter() if $may_enter; my ($pkg, @opts) = @_; state $cli_only = [qw(is-group-quota-enforced is-tree-quota-enforced is-user-quota-enforced logging logging-interval state-zapi sub-status-7g)]; my @fields = $pkg->_invalid_fields_check(@opts, _fields => $cli_only); if (@fields) { $Log->exit() if $may_exit; NACL::Exceptions::InvalidChoice->throw('Could not use ZAPI ' . 'since required_fields or filter specifies the following ' . 'attributes that can only be fetched via CLI: ' . join(', ', @fields) . "\n"); } $Log->exit() if $may_exit; } # end sub _fetch_cmode_zapi_check_common sub _fetch_cmode_zapi_check { $Log->enter() if $may_enter; my ($pkg, @opts) = @_; $pkg->_fetch_cmode_zapi_check_common(@opts); $pkg->_check_flexgroup_constituent_like_volume( @opts, interface_description => "ZAPI iter", ); $Log->exit() if $may_exit; } ## end sub _fetch_cmode_zapi_check sub _fetch_cmode_zapi_non_iter_check { $Log->enter() if $may_enter; my ($pkg, @opts) = @_; $pkg->_base_check_non_iter(@opts, _vs_context => 1); $pkg->_fetch_cmode_zapi_check_common(@opts); $Log->exit() if $may_exit; } ## end sub _fetch_cmode_zapi_non_iter_check sub _fetch_cmode_snmp { $Log->enter() if $may_enter; my $pkg = shift; my %opts = validate @_, $pkg->_fetch_backend_validate_spec(); my $orig_filter = delete $opts{filter}; my $filter = defined($orig_filter) ? {%$orig_filter} : {}; # Map any filter on the state field to one of the SNMP qvStateStat values state $state_map = { off => 'quotaStateOff', on => 'quotaStateOn', initializing => 'quotaStateInit', corrupt => 'quotaStateCorrupt', }; if (defined($filter->{state})) { # Best effort translation--if it doesn't work (e.g., because of a # query), we'll handle it with _apply_filter and the original filter. my $snmp_state = $state_map->{$filter->{state}}; if (defined($snmp_state)) { $filter->{state} = $snmp_state; } else { delete $filter->{state}; } } my $apiset = $opts{apiset}; my %orig_session_attributes = %{$apiset->get_session_attributes()}; $apiset->set_session_attributes(retries => 3, timeout => 10); my @state_objs; try { @state_objs = $pkg->SUPER::_fetch_snmp( %opts, baseoid => 'netapp1_quota_qvStateTable', map => $pkg->_common_snmp_map(), filter => $filter, ); } catch NACL::APISet::Exceptions::NoMatchingEntriesException with { $Log->debug("Caught NoMatchingEntriesException, returning empty " . "state object list to the frontend"); } finally { $apiset->set_session_attributes(%orig_session_attributes); }; foreach my $state_obj (@state_objs) { # If fetched, remap the qvStateStat value to a CLI-compatible value if ($state_obj->state_isset()) { state $rev_state_map = {reverse(%$state_map)}; my $cli_state = $rev_state_map->{$state_obj->state()}; if (!defined($cli_state)) { NACL::Exceptions::UnexpectedOutputException->throw( 'The SNMP walk for NACL::CS::VolumeQuota::fetch ' . 'returned an unexpected qvStateStat value (' . $state_obj->state() . ')' ); } $state_obj->state($cli_state); } } $pkg->SUPER::_apply_filter( state_objs => \@state_objs, filter => $orig_filter, return_on_mismatch => 1, ); $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_cmode_snmp sub _fetch_cmode_snmp_check { $Log->enter() if $may_enter; my ($pkg, %opts) = @_; my $snmp_map = $pkg->_common_snmp_map(); my %invalid_fields; foreach my $field ( ($opts{requested_fields} ? @{$opts{requested_fields}} : ()), ($opts{filter} ? keys(%{$opts{filter}}) : ())) { if (!defined($snmp_map->{$field})) { $invalid_fields{$field} = 1; } } my @invalid_fields = sort(keys(%invalid_fields)); if (@invalid_fields) { NACL::Exceptions::InvalidChoice->throw('Could not use SNMP ' . 'since required_fields or filter specifies the following ' . 'unsupported attributes: ' . join(', ', @invalid_fields) . "\n"); } if ($opts{filter} && $opts{filter}->{state} && ($opts{filter}->{state} eq 'resizing')) { NACL::Exceptions::InvalidChoice->throw('Could not use SNMP ' . "since filter specifies { state => 'resizing' } and " . "the 'resizing' state is not exposed via SNMP\n"); } $Log->exit() if $may_exit; } ## end sub _fetch_cmode_snmp_check 1;