# # Copyright (c) 2001-2013 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary VolumeSnapshot ComponentState Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::CS::VolumeSnapshot =head1 DESCRIPTION C is a derived class of L. It represents the state of ONTAP VolumeSnapshot. A related class is L, which represents access to ONTAP volume snapshot. =head1 ATTRIBUTES The individual pieces of data that are part of the state of the VolumeSnapshot element are the attributes of the VolumeSnapshot ComponentState. =over =item C<< blocks >> =item C<< busy >> =item C<< comment >> =item C<< cpcount >> =item C<< create_time >> =item C<< dsid >> =item C<< fs_version >> =item C<< internal_status >> =item C<< logical_snap_id >> Filled in for CMode CLI/ZAPI =item C<< msid >> =item C<< owners >> (Array) =item C<< physical_snap_id >> Filled in for CMode CLI/ZAPI =item C<< record_owner >> =item C<< size >> =item C<< snapshot >> =item C<< tags >> (Array) =item C<< usedblocks >> =item C<< volume >> =item C<< vserver >> =item C<< instance_uuid >> =item C<< version_uuid >> =item C<< is_7_mode >> =item C<< fsrev >> =item C<< ownblks >> =item C<< cumulative_percentage_of_used_blocks >> =item C<< cumulative_percentage_of_total_blocks >> =item C<< cumulative_total >> =item C<< dependency >> =item C<< contains_lun_clones >> =item C<< state >> Filled in for CMode CLI/ZAPI. Maps to: CMode ZAPI: For "requested_fields", "filter" and Output mapping: <$value> =item C<< cpcount_sort >> Filled in for CMode CLI. =item C<< test_version_uuid >> Filled in for CMode CLI. =item C<< foreground >> Filled in for CMode CLI. =item C<< size_kb >> Filled in for CMode CLI. =item C<< fs_block_format >> Filled in for CMode CLI. =item C<< is_constituent >> Filled in for CMode CLI/ZAPI. Maps to: CMode ZAPI: For "requested_fields", "filter" and Output mapping: <$value> =item C<< snapmirror_label >> Filled in for CMode CLI. =item C<< retention_date >> Filled in for 7Mode CLI only. =item C<< status >> Filled in for 7Mode CLI only. =item C<< vol_dsid >> Filled in for CMode CLI. =item C<< percent-reserved >> Filled in for CMode ZAPI. =item C<< vol_provenance_uuid >> Filled in for CMode CLI/ZAPI. =item C<< node >> *Node Filled in for CMode CLI. =item C<< err_vs >> Filled in for CMode CLI. =item C<< err_code >> Filled in for CMode CLI. =item C<< err_reason >> Filled in for CMode CLI. =item C<< err_vol >> Filled in for CMode CLI. =item C<< is_flexgroup_qtree_enabled >> Filled in for CMode CLI. =back =cut package NACL::CS::VolumeSnapshot; use strict; use warnings; use Params::Validate qw(validate); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use NACL::APISet::Exceptions::InvalidParamValueException qw(:try); use NACL::Exceptions::InvalidChoice; use NATE::BaseException; use NACL::CS::ComponentState::ZapiSkip qw(make_zapi_skip); use NACL::CS::ComponentState::ZapiArray qw(make_zapi_array); use base 'NACL::CS::ComponentState::ONTAP'; use NACL::CS::_Mixins::ComponentState qw( sort_by_newest sort_by_oldest _arrange_by_create_time ); use NACL::Global; use NACL::C::UnitNormalization; use Data::Dumper; use Class::MethodMaker [ scalar => 'blocks', scalar => 'busy', scalar => 'comment', scalar => 'cpcount', scalar => 'create_time', scalar => 'dsid', scalar => 'fs_version', scalar => 'internal_status', scalar => 'logical_snap_id', scalar => 'msid', array => 'owners', scalar => 'physical_snap_id', scalar => 'record_owner', scalar => 'size', scalar => 'snapshot', array => 'tags', scalar => 'usedblocks', scalar => 'volume', scalar => 'vserver', scalar => 'instance_uuid', scalar => 'version_uuid', scalar => 'is_7_mode', scalar => 'state', # specific to CMode ZAPI scalar => 'cumulative_percentage_of_used_blocks', scalar => 'cumulative_percentage_of_total_blocks', scalar => 'cumulative_total', scalar => 'dependency', scalar => 'contains_lun_clones', scalar => 'percent_reserved', # specific to 7Mode scalar => 'fsrev', scalar => 'ownblks', scalar => 'status', scalar => 'cpcount_sort', scalar => 'test_version_uuid', scalar => 'foreground', scalar => 'size_kb', scalar => 'fs_block_format', scalar => 'is_constituent', scalar => 'snapmirror_label', scalar => 'retention_date', scalar => 'snapshot_date', scalar => 'vol_dsid', scalar => 'vol_provenance_uuid', scalar => 'node', scalar => 'err_vs', scalar => 'err_code', scalar => 'err_reason', scalar => 'err_vol', scalar => 'is_flexgroup_qtree_enabled', scalar => 'progress', scalar => 'name' ]; sub isa { $Log->enter() if $may_enter; my ($pkg_or_obj, $kind) = @_; my $isa = $pkg_or_obj->_build_isa( kind => $kind, alias => 'NACL::CS::Snapshot' ); $Log->exit() if $may_exit; return $isa; } =head1 METHODS =head2 fetch my $VolumeSnapshot_state = NACL::CS::VolumeSnapshot->fetch(command_interface => $ci, ...); my @VolumeSnapshot_states = NACL::CS::VolumeSnapshot->fetch(command_interface => $ci, ...); (Class method) Discovers which elements are present and returns their state in ComponentState objects. Called in scalar context it returns only one state object, in list context it returns all state objects. See L for a more detailed description along with a complete explanation of the options it accepts. =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, @args) = @_; my @state_objs = $pkg->SUPER::fetch( @args, choices => [ { method => '_fetch_cmode_cli', interface => 'CLI', set => 'CMode', }, { method => '_fetch_cmode_zapi', interface => 'ZAPI', set => 'CMode', check => '_cmode_zapi_check', }, { method => '_fetch_7mode_cli', interface => 'CLI', set => '7Mode|Nodescope', }, ], show_cmd => 'volume snapshot show', exception_text => 'No matching volume snapshot(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, %args) = @_; my $time = $args{filter}->{'create-time'}; if ((defined $time) && ($time =~ /^\d+$/)) { # create_time is specified in unix time format. # Perform the conversion. my $output = $pkg->convert_to_system_time( unixtime => $time, command_interface => $args{command_interface}, interface => 'CLI' ); # Build the time string: Thu Mar 24 17:05:03 2011 my $wday = $pkg->enumerate_week( wday => $output->{'day_of_week'}, reverse => 1 ); my $mon = $pkg->enumerate_month( month => $output->{'month'}, reverse => 1 ); $args{filter}->{'create-time'} = "\"$wday $mon $output->{day} " . "$output->{hour}:$output->{minute}:$output->{second} " . $output->{'year'} . '"'; } ## end if ( ( defined $time )... # CMode understands the units and so we don't have to normalize 'size'. # Unfortunately later, the call to _apply_filter() wont match the objects # as the o/p returned by CMode will be in bytes and _apply_filter will see # it as a mismatch as its not understanding the units as of now. This # will be fixed as part of burt440070. my @state_objs = $pkg->SUPER::_fetch_cmode_cli(%args, api => "volume_snapshot_show"); $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_cmode_cli 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 ($snap_list_b_response, $snap_list_response, $caught_exception, $snap_list_output, $snap_list_b_output, $snap_status_id, $snap_list_l_response, $snap_list_l_output, $snap_status_id_output, $snap_list_n_response, $snap_list_n_output, ); my %copy_filter = %{$opts{filter}}; my @copy_requested_fields = @{$opts{requested_fields}}; my $deleted_filter = $pkg->_remove_relational_regex_filters( filter => \%copy_filter, requested_fields => \@copy_requested_fields ); my $filter = \%copy_filter; my $requested_fields = \@copy_requested_fields; my ($req_field_filter, @state_objs); $req_field_filter->{'requested_fields'} = $requested_fields; $req_field_filter->{'filter'} = \%copy_filter; my $fields_only_in_snap_list = [qw (blocks usedblocks)]; my $fields_only_in_snap_list_b = [qw (owners)]; my $fields_only_in_snap_list_l = [qw (retention-date)]; my $fields_only_in_snap_status_i = [ qw (status fsrev ownblks create-time fs-version logical-snap-id physical-snap-id) ]; my %args; $args{"connectrec-timeout"} = 1200; $args{"vol-name"} = $filter->{volume} if (defined $filter->{volume}); my $want_only_pk = 1; my %new_hash = (); if (@$requested_fields) { my $c_pkg = $pkg->get_C_package_name(); eval "use $c_pkg"; if ($@) { NATE::BaseException->throw( "Error in loading the package $c_pkg\n" . $@); } my @primary_keys = $c_pkg->get_primary_keys(); my %pk_hash; map { $pk_hash{$_} = 1; } @primary_keys; my @all_wanted_fields = (@$requested_fields, (keys %$filter)); foreach my $field (@all_wanted_fields) { if (!exists $pk_hash{$field}) { # A non-primary key is needed $want_only_pk = 0; last; } } } else { $want_only_pk = 0; } if ($want_only_pk) { $snap_list_n_response = $apiset->snap_list( %args, 'no-snapshot-space-consumption' => 1, # -n option ); $snap_list_n_output = $snap_list_n_response->get_parsed_output(); foreach my $val (@{$snap_list_n_output}) { foreach my $snap (@{$val->{snapshot_info}}) { # snapshot name is unique only within volume # so snapshot name cannot be used as key # VolumeName:snapshot_name is unique key combination my $vol_snap = $val->{volume} . ":" . $snap->{snapshot_name}; if (exists($new_hash{$vol_snap})) { %{$new_hash{$vol_snap}} = (%{$new_hash{$vol_snap}}, %{$snap}); } else { %{$new_hash{$vol_snap}} = %{$snap}; } $new_hash{$vol_snap}->{volume} = $val->{volume}; } } } else { if ($pkg->_want_any_field_of( %{$req_field_filter}, fields_filled_by_api => $fields_only_in_snap_list ) ) { $snap_list_response = $apiset->snap_list(%args); $snap_list_output = $snap_list_response->get_parsed_output(); foreach my $val (@{$snap_list_output}) { foreach my $snap (@{$val->{snapshot_info}}) { # VolumeName:snapshot_name is unique key combination my $vol_snap = $val->{volume} . ":" . $snap->{snapshot_name}; if (exists($new_hash{$vol_snap})) { %{$new_hash{$vol_snap}} = (%{$new_hash{$vol_snap}}, %{$snap}); } else { %{$new_hash{$vol_snap}} = %{$snap}; } $new_hash{$vol_snap}->{volume} = $val->{volume}; } } } if ($pkg->_want_any_field_of( %{$req_field_filter}, fields_filled_by_api => $fields_only_in_snap_list_b ) ) { $snap_list_b_response = $apiset->snap_list( %args, 'busy' => 1, # -b option ); $snap_list_b_output = $snap_list_b_response->get_parsed_output(); foreach my $val (@{$snap_list_b_output}) { foreach my $snap (@{$val->{snapshot_info_b}}) { # VolumeName:snapshot_name is unique key combination my $vol_snap = $val->{volume} . ":" . $snap->{snapshot_name}; if (exists($new_hash{$vol_snap})) { %{$new_hash{$vol_snap}} = (%{$new_hash{$vol_snap}}, %{$snap}); } else { %{$new_hash{$vol_snap}} = %{$snap}; } $new_hash{$vol_snap}->{volume} = $val->{volume}; } } } if ($pkg->_want_any_field_of( %{$req_field_filter}, fields_filled_by_api => $fields_only_in_snap_status_i ) ) { foreach my $val (@{$req_field_filter->{'requested_fields'}}) { my $word = "logical-snap-id"; $args{"volume-logical-snapid"} = 1 if ($val =~ /$word/); } $snap_status_id = $apiset->snap_status(%args); $snap_status_id_output = $snap_status_id->get_parsed_output(); foreach my $val (@{$snap_status_id_output}) { foreach my $snap (@{$val->{snapshot_status}}) { # VolumeName:snapshot_name is unique key combination my $vol_snap = $val->{volume} . ":" . $snap->{snapshot_name}; if (exists($new_hash{$vol_snap})) { %{$new_hash{$vol_snap}} = (%{$new_hash{$vol_snap}}, %{$snap}); } else { %{$new_hash{$vol_snap}} = %{$snap}; } $new_hash{$vol_snap}->{volume} = $val->{volume}; } } } if ($pkg->_want_any_field_of( %{$req_field_filter}, fields_filled_by_api => $fields_only_in_snap_list_l ) ) { $snap_list_l_response = $apiset->snap_list( %args, 'snap-lock' => 1, # -l option ); $snap_list_l_output = $snap_list_l_response->get_parsed_output(); foreach my $val (@{$snap_list_l_output}) { next unless (scalar @{$val->{snapshot_info}}); foreach my $snap (@{$val->{snapshot_info}}) { # VolumeName:snapshot_name is unique key combination my $vol_snap = $val->{volume} . ":" . $snap->{snapshot_name}; if (exists($new_hash{$vol_snap})) { %{$new_hash{$vol_snap}} = (%{$new_hash{$vol_snap}}, %{$snap}); } else { %{$new_hash{$vol_snap}} = %{$snap}; } $new_hash{$vol_snap}->{volume} = $val->{volume}; } } } ## If none of the switches were used to populate the result ## output run the default snap list command and return the output if (!keys %new_hash) { $snap_list_response = $apiset->snap_list(%args); $snap_list_output = $snap_list_response->get_parsed_output(); foreach my $val (@{$snap_list_output}) { foreach my $snap (@{$val->{snapshot_info}}) { # VolumeName:snapshot_name is unique key combination my $vol_snap = $val->{volume} . ":" . $snap->{snapshot_name}; %{$new_hash{$vol_snap}} = %{$snap}; $new_hash{$vol_snap}->{volume} = $val->{volume}; } } } } while (my ($key, $value) = each %new_hash) { my $final_attributes = $pkg->_hash_copy( source => $value, copy => [ qw(name progress status volume fsrev ownblks owners retention_date) ], map => { "date" => "create_time", "snapshot_date" => "create_time", "snapshot_name" => "snapshot", "release" => "fs_version", "snapid" => "physical_snap_id", "logical_snapid" => "logical_snap_id", "%/total" => "blocks", "%/used" => "usedblocks", }, ); my $obj = $pkg->new(command_interface => $opts{command_interface}); $obj->_set_fields(row => $final_attributes); push @state_objs, $obj; } $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_7mode_cli sub _cmode_zapi_check { $Log->enter() if $may_enter; my ($pkg, %opts) = @_; my $release = $opts{'command_interface'}->version_manager()->is_version_lt( release => $NACL::Global::ONTAP_Release_Longboard_0 ); my @fields = (qw (tags is-flexgroup-qtree-enabled) ); my @cli_only_fields = $pkg->_invalid_fields_check(%opts, _fields => \@fields); if (@cli_only_fields) { my $msg='Cannot use the ZAPI Implementation of ' . "NACL::CS::VolumeSnapshot because the fields '" . join(",", @cli_only_fields) . "' provided in " . 'requested_fields or filter are not available via ZAPI'; $Log->comment($msg); NACL::Exceptions::InvalidChoice->throw($msg); } if ($release && (exists $opts{filter}->{comment} || grep {$_ eq 'comment'} @{$opts{requested_fields}})) { my $msg = 'Could not use the ZAPI implementation of ' . "NACL::CS::VolumeSnapshot because the field 'comment' " . 'was provided in requested_fields or filter, but the ZAPI ' . 'currently does not return this value (see burt 812556)'; $Log->exit() if $may_exit; $Log->comment($msg); NACL::Exceptions::InvalidChoice->throw($msg); } $Log->exit() if $may_exit; } sub _fetch_cmode_zapi { $Log->enter() if $may_enter; my ($pkg, %opts) = @_; my $filter = $opts{filter}; # total - Number of 1024 byte blocks in the snapshot (ZAPI) # size - Number of bytes in the snapshot (CLI) if (defined $filter->{size}) { my %filter = %{$opts{filter}}; # Inputs should be normalized to the format as expected by # CMode ZAPI $filter{size} = NACL::C::UnitNormalization::to_kilobytes($filter{size}); # The unit 'KB' shouldn't be specified for ZAPI $filter{size} =~ s/((?i)KB)$//; $opts{filter} = \%filter; } ## end if ( defined $filter->... my @ret_objs = $pkg->SUPER::_fetch_cmode_zapi( %opts, api => "snapshot_get_iter", map => { "snapshot" => 'name', "instance-uuid" => 'snapshot-instance-uuid', "size" => 'total', "version-uuid" => 'snapshot-version-uuid', "cumulative-total" => 'cumulative-total', "is-7-mode" => 'is-7-mode-snapshot', "create-time" => 'access-time', # This is a typedef-array, so only the first key should be # specified on the input side (snapshot-owners-list). "owners" => [ make_zapi_array('snapshot-owners-list'), make_zapi_skip('snapshot-owner'), make_zapi_skip('owner'), ], "percent-reserved" => ['snapshot-reserve-detail-info', 'percentage'], "usedblocks" => 'percentage-of-used-blocks', "blocks" => 'percentage-of-total-blocks', "size" => 'total', "is-constituent" => 'is-constituent-snapshot', 'physical-snap-id' => 'snapshot-instance-uuid', 'logical-snap-id' => 'snapshot-version-uuid', 'vol-provenance-uuid' => 'volume-provenance-uuid' }, copy => [ qw(volume vserver contains-lun-clones busy dependency snapmirror-label cumulative-percentage-of-used-blocks cumulative-percentage-of-total-blocks cumulative-total state) ] ## end ); $Log->exit() if $may_exit; return @ret_objs; } ## end sub _fetch_cmode_zapi sub _update_state_objs_cmode_zapi { $Log->enter() if $may_enter; my ($pkg, %opts) = @_; $pkg->SUPER::_update_state_objs_cmode_zapi( %opts, additional_translations => { size_to_bytes => { method => '_zapi_output_translate_size', fields_to_translate => ['size'] }, }, zapi_field_translations => { timestamp_to_string => ['create-time'], add_percentage => [qw(blocks usedblocks)], }, ); $Log->exit() if $may_exit; } # ZAPI returns the size field in terms of KB, but CLI returns it in terms # of bytes, so multiply value by 1024 sub _zapi_output_translate_size { my ($pkg, $val) = @_; $val *= 1024; return $val; } =head2 sort_by_newest my @sorted_by_newest = NACL::CS::VolumeSnapshot->sort_by_newest(state_objs => \@vol_cs_objs); (Applicable only for CMode) This subroutine arranges NACL::CS::VolumeSnapshot objects with the newest ones listed first. Note that this requires all state objects to have their C attribute populated. =over =item Options =over =item C<< state_objs => \@vol_cs_objs >> An array-reference of objects of type C. Each of these objects must have their C attribute populated. =back =back =head2 sort_by_oldest my @sorted_by_oldest = NACL::CS::VolumeSnapshot->sort_by_oldest(state_objs => \@vol_cs_objs); (Applicable only for CMode) Similar to L but with the state objects being arranged with the oldest ones listed first. =cut 1;