# # Copyright (c) 2001-2011 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary VolumeMove Component Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::CS::VolumeMove =head1 DESCRIPTION C is a derived class of L. It represents the state of an ONTAP Volume move. A related class is L, which represents access to an ONTAP Volume move. =head1 ATTRIBUTES The attributes for both CMode CLI and ZAPI are very close however some minor differences exist such as some strings having double quotes around the contents. =over =item C<< co_attempts >> =item C<< source >> B. Retained for backwards compatibility. The field C should be used instead. This field (C) is not mode/interface agnostic and is filled in only for 7Mode-CLI. =item C<< co_time >> =item C<< destination >> This is a 7Mode-CLI only field, which is the value of the "destination" field that shows up in the output of "vol move status". This field is of the form: ":" The is the temporary volume created on the destination aggregate while the volume is being moved. Note that to get just the name of the destination aggregate, the field C should be used. =item C<< state >> =item C<< estimated_completion_time >> Filled in for CMode CLI. =item C<< completion_status >> Filled in for CMode CLI. =item C<< managing_node >> Filled in for CMode CLI. =item C<< source_node >> Filled in for CMode CLI. =item C<< bytes_remaining >> Filled in for CMode CLI. =item C<< job_uuid >> Filled in for CMode CLI. =item C<< bytes_sent >> Filled in for CMode CLI. =item C<< destination_aggregate >> Filled in for CMode CLI. =item C<< actual_duration >> Filled in for CMode CLI. =item C<< cutovers_soft_deferred >> Filled in for CMode CLI. =item C<< details >> Filled in for CMode CLI. =item C<< job_id >> Filled in for CMode CLI. =item C<< estimated_remaining_duration >> Filled in for CMode CLI. =item C<< cutovers_hard_deferred >> Filled in for CMode CLI. =item C<< cutover_window >> Filled in for CMode CLI. =item C<< internal_state >> Filled in for CMode CLI. =item C<< actual_completion_time >> Filled in for CMode CLI. =item C<< start_time >> Filled in for CMode CLI. =item C<< phase >> Filled in for CMode CLI. =item C<< cutover_action >> Filled in for CMode CLI. =item C<< last_cutover_trigger_time >> Filled in for CMode CLI. =item C<< vserver >> Filled in for CMode CLI. =item C<< percent_complete >> Filled in for CMode CLI. =item C<< execution_progress >> Filled in for CMode CLI. =item C<< source_aggregate >> Filled in for CMode CLI. =item C<< cutovers_attempted >> Filled in for CMode CLI. =item C<< prior_issues >> Filled in for CMode CLI. =item C<< completion_code >> Filled in for CMode CLI. =item C<< cutover_trigger_time >> Filled in for CMode CLI. =item C<< destination_node >> Filled in for CMode CLI. =item C<< replication_throughput >> Filled in for CMode CLI. =item C<< volume_uuid >> *Volume Instance UUID Filled in for CMode CLI. =item C<< moved_by_autobalance >> Move initiated by balancer possible value(s) are, true,false Filled in for CMode CLI. =item C<< skip_delta_calculation >> Skip the Delta Calculation possible value(s) are, true,false Filled in for CMode CLI. =item C<< bypass_throttling >> Bypass Replication Engine Throttling possible value(s) are, true,false Filled in for CMode CLI. =back =cut package NACL::CS::VolumeMove; use strict; use warnings; 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 Params::Validate qw(validate); use POSIX qw( strftime ); use NACL::APISet::Exceptions::ResponseException qw(:try); use NACL::CS::Volume; use base 'NACL::CS::ComponentState::ONTAP'; use Class::MethodMaker [ scalar => 'cutover_attempts', scalar => 'volume', scalar => [ { -default_ctor => sub { if (ref $_[0]) { $Log->warn("The accessor method 'source' was invoked. " . 'Note that this field is deprecated and has ' . 'been retained only for backwards compatibility.' . ' The mode/interface agnostic field to be used ' . "is 'volume'. Please modify the call to invoke " . "the 'volume' accessor method"); return $_[0]->volume(); } ## end if ( ref $_[0] ) }, }, 'source', ], scalar => 'cutover_time', scalar => 'destination', scalar => 'state', scalar => 'last_completed_time_taken', scalar => 'last_completed_data_transferred', scalar => 'current_transfer_size', scalar => 'estimated_completion_time', scalar => 'completion_status', scalar => 'managing_node', scalar => 'source_node', scalar => 'bytes_remaining', scalar => 'job_uuid', scalar => 'bytes_sent', scalar => 'destination_aggregate', scalar => 'actual_duration', scalar => 'cutovers_soft_deferred', scalar => 'details', scalar => 'job_id', scalar => 'estimated_remaining_duration', scalar => 'cutovers_hard_deferred', scalar => 'cutover_window', scalar => 'internal_state', scalar => 'actual_completion_time', scalar => 'start_time', scalar => 'phase', scalar => 'cutover_action', scalar => 'last_cutover_trigger_time', scalar => 'vserver', scalar => 'percent_complete', scalar => 'execution_progress', scalar => 'source_aggregate', scalar => 'cutovers_attempted', scalar => 'prior_issues', scalar => 'completion_code', scalar => 'cutover_trigger_time', scalar => 'destination_node', scalar => 'replication_throughput', scalar => 'volume_uuid', scalar => 'moved_by_autobalance', scalar => 'skip_delta_calculation', scalar => 'bypass_throttling', ]; =head1 METHODS =head2 fetch my $volmove_state = NACL::CS::VolumeMove->fetch(command_interface=>$ci,...); my @volmove_states = NACL::CS::VolumeMove->fetch(command_interface=>$ci,...); see L Uses a 7Mode CLI APISet, CMode CLI APISet, or CMode ZAPI APISet =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 = shift; my @state_objs = $pkg->SUPER::fetch( @_, show_cmd => 'volume move show', choices => [ { method => '_fetch_7mode_cli', interface => 'CLI', set => '7Mode', }, { method => '_fetch_cmode_cli', interface => 'CLI', set => 'CMode', }, { method => '_fetch_cmode_zapi', interface => 'ZAPI', set => 'CMode', }, ], exception_text => 'No matching volume move(s) found' ); $Log->exit() if $may_exit; return wantarray ? @state_objs : $state_objs[0]; } ## end sub fetch sub _fetch_7mode_cli { $Log->enter() if $may_enter; my $pkg = shift; my @state_objs; my %opts = validate @_, $pkg->_fetch_backend_validate_spec(); my $apiset = $opts{apiset}; # Strip special characters understood by CMode my @modified_requested_fields = @{$opts{requested_fields}}; my %modified_filter = %{$opts{filter}}; $pkg->_remove_relational_regex_filters( filter => \%modified_filter, requested_fields => \@modified_requested_fields ); my $requested_fields = \@modified_requested_fields; my $filter = \%modified_filter; my %req_field_filter = (requested_fields => $requested_fields, filter => $filter); my %api_inputs; my $response; my $caught_exception = 0; if ($pkg->_want_any_field_of( %req_field_filter, fields_filled_by_api => [ qw(last_completed_time_taken last_completed_data_transferred current_transfer_size ) ], ) ) { $api_inputs{verbose} = 'true'; } ## end if ( $pkg->_want_any_field_of...) if (exists $filter->{volume}) { $api_inputs{volume} = $filter->{volume}; } elsif (exists $filter->{source}) { $Log->warn("The field 'source' was specified in the filter. " . 'Note that this field is deprecated and has been ' . 'retained only for backwards compatibility. The ' . "mode/interface agnostic field to be used is 'volume'. " . "Please modify the call to send 'volume' in the filter instead " . "of 'source'"); $api_inputs{volume} = $filter->{source}; } ## end elsif ( exists $filter->{...}) try { $response = $apiset->vol_move_status(%api_inputs); } catch NACL::APISet::Exceptions::InvalidParamValueException with { $caught_exception = 1; } catch NACL::APISet::Exceptions::CommandFailedException with { my $exception = shift; my $error_text = $exception->text(); if ($error_text =~ /Specified source volume is not being moved/) { $caught_exception = 1; } else { $exception->throw(); } }; if ($caught_exception) { $Log->exit() if $may_exit; return; } my $output = $response->get_parsed_output(); foreach my $row (@$output) { my $obj = $pkg->new(command_interface => $opts{command_interface}); my $new_row = {}; if ($pkg->_want_any_field_of( %req_field_filter, fields_filled_by_api => ['source-aggregate'] ) ) { # "vol move status" doesn't tell us the source-aggregate, we need # to query the Volume CS for this information my $vol_cs = NACL::CS::Volume->fetch( command_interface => $opts{command_interface}, requested_fields => [qw(aggregate)], filter => {volume => $row->{source}}, ); $new_row->{source_aggregate} = $vol_cs->aggregate(); } ## end if ( $pkg->_want_any_field_of...) $row->{destination} =~ /(.*):.*/; $new_row->{destination_aggregate} = $1; $pkg->_hash_copy( source => $row, target => $new_row, copy => [ qw(cutover_attempts cutover_time state current_transfer_size destination) ], map => {source => 'volume',}, ); if (exists $row->{last_completed_transfer}) { $pkg->_hash_copy( source => $row->{last_completed_transfer}[0], target => $new_row, map => { time_taken => 'last_completed_time_taken', data_transferred => 'last_completed_data_transferred' } ); } ## end if ( exists $row->{last_completed_transfer...}) $obj->_set_fields(row => $new_row); 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_cli { $Log->enter() if $may_enter; my $pkg = shift; my @state_objs = $pkg->SUPER::_fetch_cmode_cli(@_, api => 'volume_move_show',); $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_cmode_cli sub _fetch_cmode_zapi { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my $field_map = { 'cutover-attempts' => 'cutover-attempts', 'volume' => 'volume', 'source-node' => 'source-node', # I don't see this field in SN and RR build. Should I remove this # mapping? 'cutover-time' => 'cutover-trigger-timestamp', 'destination-node' => 'destination-node', 'state' => 'state', 'estimated-completion-time' => 'estimated-completion-time', 'completion-status' => 'completion-status', 'managing-node' => 'managing-node', 'source-node' => 'source-node', 'bytes-remaining' => 'bytes-remaining', 'job-uuid' => 'job-uuid', 'bytes-sent' => 'bytes-sent', 'destination-aggregate' => 'destination-aggregate', 'actual-duration' => 'actual-duration', 'cutovers-soft-deferred' => 'cutovers-soft-deferred-count', 'details' => 'details', 'job-id' => 'job-id', 'estimated-remaining-duration' => 'estimated-remaining-duration', 'cutovers-hard-deferred' => 'cutover-hard-deferred-count', 'cutover-window', => 'cutover-window', 'internal-state' => 'internal-state', 'actual-completion-time' => 'actual-completion-timestamp', 'start-time' => 'start-timestamp', 'phase' => 'phase', 'cutover-action' => 'cutover-action', 'vserver' => 'vserver', 'percent-complete' => 'percent-complete', 'execution-progress' => 'execution-progress', 'source-aggregate' => 'source-aggregate', 'cutovers-attempted' => 'cutover-attempted-count', 'prior-issues' => 'prior-issues', 'completion-code' => 'completion-code', 'cutover-trigger-time' => 'cutover-trigger-timestamp', 'last-cutover-trigger-time' => 'last-cutover-trigger-timestamp', 'replication-throughput' => 'replication-throughput', 'volume-uuid' => 'volume-instance-uuid', }; my @state_objs = $pkg->SUPER::_fetch_cmode_zapi( @args, api => 'volume_move_get_iter', map => $field_map, ); my @fixup_fields = keys %$field_map; my $timezone; my $ci; if (@state_objs) { $ci = $state_objs[0]->command_interface; } foreach my $state_obj (@state_objs) { foreach my $field (@fixup_fields) { my $field_isset = $field . '_isset'; $field_isset =~ s/-/_/g; if ($state_obj->$field_isset && ($state_obj->$field() eq '')) { # I keep this inner if-elsif-else clauses only for # backward compatibility reasons! # I don't see the correctness of it being here. if ($field and ( $field eq 'cutovers-soft-deferred' || $field eq 'cutovers-hard-deferred') ) { $state_obj->$field(0); } elsif ($field eq 'cutover-time') { # Leave this field empty } else { $state_obj->$field('-'); } next; } elsif ((!$state_obj->$field_isset) || ($state_obj->$field eq '-')) { # The field was requested and ZAPI hadn't populated this tag in # the output XML, so the base class had set the value to be # '-' and made it, look like CM CLI or if the field was not # requested by the user in the first place, hence ZAPI # didn't bother to return it back in which case the attribute # is not filled up in the CS object. # This elsif clause will help us to skip the following 'else' # clause which does additional processing on the output values, # if one of the above conditions is true. next; } elsif ($field) { if ($field =~ /percent/) { $state_obj->$field($state_obj->$field() . '%'); } elsif ($field =~ /^details$|^execution-progress$/) { $state_obj->$field('"' . $state_obj->$field() . '"'); } elsif ($field =~ /-time$/) { unless (defined $timezone) { # Get the timezone only if the code reaches here. $timezone = $ci->get_timezone(); } my $formatted_hash = $pkg->convert_to_system_time( unixtime => $state_obj->$field, timezone => $timezone ); my $value = $pkg->enumerate_week( wday => $formatted_hash->{day_of_week}, reverse => 1 ) . ' ' . $pkg->enumerate_month( month => $formatted_hash->{month}, reverse => 1 ) . ' ' . "$formatted_hash->{day} $formatted_hash->{hour}:$formatted_hash->{minute}:$formatted_hash->{second} $formatted_hash->{year}"; $state_obj->$field('"' . $value . '"'); } elsif ($field eq 'actual-duration' || $field eq 'estimated-remaining-duration') { # This field was wrongly getting formatted as dd:dd # earlier while the actual CLI format is dd:dd:dd.ddd. We # will anyways not be able to achive the milliseconds # accuracy in the duration obtained from ZAPI as the value # was returned back in seconds. If one is comparing this # field, then advanced comparing logic (which ignores # milliseconds) has to be added. # We also need additional code to handle this format in # the input side # say, if one passes, # filter => {'actual-duration' => $value_in_cli_format} # This ZAPI doesn't understand the value passed in CLI # format, but I belive noone would filter on these # fields which change continuously and so this additional # code need not be handled for now. $state_obj->$field( sprintf("%02u:%02u:%02u", $state_obj->$field() / 60 / 60 / 60, $state_obj->$field() / 60 / 60, $state_obj->$field() / 60) ); } ## end elsif ( $field eq 'actual-duration'...) } #} ## end else [ if ( !defined $state_obj...)] } ## end foreach my $field (@fixup_fields) } ## end foreach my $state_obj (@state_objs) $Log->exit() if $may_exit; return @state_objs; } ## end sub _fetch_cmode_zapi 1;