# # Copyright (c) 2001-2013 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary Qtree Task Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here package NACL::STask::Qtree; use strict; use warnings; use base qw(NACL::C::Qtree NACL::STask::STask); use Tharn qw( snooze); 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 SCALAR BOOLEAN SCALARREF); use NATE::Exceptions::Argument qw(:try); use NACL::ComponentUtils qw(_optional_scalars); use NACL::C::Exceptions::VolumeQtree::AlreadyExists (); use NACL::Exceptions::VerifyFailure (); use NACL::C::Exceptions::VolumeQtree::DoesNotExist (); use NACL::C::Exceptions::VolumeQtree::QtreeContainsLuns (); use NACL::APISet::Exceptions::ResponseException (); use NACL::Exceptions::UnexpectedState qw(:try); use constant DEFAULT_TIMEOUT => 300; use constant POLL_DELTA => 3; use Class::MethodMaker [ scalar => [ { -type => 'NACL::C::Job' }, 'job_component', ], ]; BEGIN { # Earlier, the job stored in the object was called "last_job". To # keep consistency with the naming convention, this has been renamed to # "job_component". For backwards compatibility, all of the last_job_* # methods have been aliased to the job_component_* methods. # (Class::MethodMaker installs "isset", "reset" and "clear" methods for # any scalar element) no strict 'refs'; *{last_job} = \&job_component; foreach my $suffix (qw(isset reset clear)) { my $old_method = "last_job_$suffix"; my $new_method = "job_component_$suffix"; *{$old_method} = \&{$new_method}; } use strict 'refs'; } =head1 NAME NACL::STask::Qtree =head1 DESCRIPTION C provides a number of well-defined but potentially complex or multi-step methods related to Qtrees 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 Qtree-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, 'create', 'modify', 'rename'. Cleanup methods are, Qtree Method Cleanup Method -------------------------------------- create purge modify modify rename rename =head1 ATTRIBUTES =head2 command_interface (Required) A component object that represents the host to which to send commands. =head2 qtree (Required) The name of the qtree. =head2 volume (Required) The name of the volume on which the qtree will reside. =head2 vserver (Required for cmode) The name of the vserver containing the volume. =head2 job_component The C instance representing the last job started while operating on this Qtree. This is unlikely to be needed by callers, but is required for the task implementation. This gets filled in only in CMode. =head1 METHODS =head2 create my $qtree_obj = NACL::STask::Qtree->create( command_interface => $command_interface, qtree => $qtree_name, vserver => $vserver_name, volume => $volume_name, nacltask_if_exists => $action, # default 'die' nacltask_to_cleanup => 1, # default '0' nacltask_cleanup_manager => $cleanupobj, %other_options ); (Class method) Create a qtree. This method provides additional services beyond what's inherent to the product's qtree creation commands, as controlled and described by the new C. =over =item Options =over =item C<< nacltask_if_exists => $action >> (Optional, defaults to "die") Specifies an action to be taken if the qtree already exists. =over =item C When set to "die", an exception is thrown if the qtree already exists. =item C If a qtree of the same name already exists, reuse it. =item C If a qtree of the same name already exists, purge it and create a new one. =back =item C<< nacltask_verify => 0|1 >> (Optional, defaults to 0) =over =item When set to 0, verification is not performed. =item When set to 1, it is verified that the created qtree has all of the attributes specified. =back Note that it is also possible to perform the verification later by calling L. This can be used for cases where ONTAP chooses to not set all attributes to the exact values specified. In the call we can specify those attributes we want verified. =item C<< _was_created => \$scalar >> (Optional) When this option is provided a reference to a scalar variable, the variable gets filled in with a boolean value describing whether the volume was found (value will be 0; this scenario is possible when if_exists => "reuse") or whether the aggregate was created (value will be 1). This is necessary to determine whether the aggregate needs to be cleaned up later. my $was_created; my $qtree_obj = NACL::STask::Qtree->create( if_exists => 'reuse', _was_created => \$was_created, %other_opts ); # Operate on $qtree_obj here # ... # Now determine whether to clean up the qtree, since we're not sure # whether we reused an existing aggregate or created a new one if ($was_created) { # New volume was created. Clean it up. $vol_obj->purge(); } All of the other various options are described in L<< NACL::C::VolumeQtree->create|lib-NACL-C-VolumeQtree-pm/create >> =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 =over =item Exceptions =over =item C This type of exception is thrown when an attempt is made to a create qtree that already exists. =item C This type of exception is thrown when verification fails for the created qtree. =back =cut sub create { $Log->enter() if $may_enter; my $pkg = shift; my %opts = $pkg->_common_validate_with( params => \@_, additional_spec => { # Kept for backwards compatibility if_exists => { type => SCALAR, callbacks => $pkg->_if_exists_validate_callback(), optional => 1 }, nacltask_if_exists => { type => SCALAR, callbacks => $pkg->_if_exists_validate_callback(), optional => 1 }, nacltask_verify => { type => SCALAR, default => 0 }, _was_created => { type => SCALARREF, optional => 1 }, $pkg->_cleanup_validate_spec(), }, allow_extra => 1, ); # Allow for backwards compatibility for the "if_exists" option while # making nacltask_if_exists the recommended option to use my $if_exists = delete $opts{nacltask_if_exists} || $opts{if_exists} || 'die'; delete $opts{if_exists}; my $command_interface = $opts{command_interface}; my $verify = delete $opts{nacltask_verify}; my $was_created = delete $opts{_was_created}; $$was_created = 0; my ( %opts_for_cleanup, %opts_for_register, $nacltask_to_cleanup); $pkg->_move_common_cleanup_opts( source => \%opts, target => \%opts_for_cleanup, ); my $self; CREATE: { use warnings qw(exiting); try { $self = $pkg->SUPER::create(%opts); $$was_created = 1; if ($verify) { $pkg->taskverify_create(%opts); } } catch NACL::C::Exceptions::VolumeQtree::AlreadyExists with { my $exception = shift; $Log->trace('The qtree already exists'); $self = $pkg->_element_exists_handler( create_opts => \%opts, nacltask_if_exists => $if_exists, exception => $exception ); if ( !$self ) { no warnings qw(exiting); redo CREATE; } }; } # END CREATE # Register this resource with the cleanup manager for cleanup. $pkg->_copy_common_opts_for_cleanup( 'source' => {%opts, %opts_for_cleanup}, 'target' => \%opts_for_register, 'nacltask_to_cleanup' => \$nacltask_to_cleanup, 'to_cleanup' => 'purge', ); $pkg->_register_for_cleanup(%opts_for_register) if ($nacltask_to_cleanup && $$was_created); $Log->exit() if $may_exit; return $self; } =head2 modify NACL::STask::Qtree->modify( command_interface => $command_interface, qtree => $qtree_name, vserver => $vserver_name, volume => $volume_name, [nacltask_verify => 0|1], nacltask_to_cleanup => 1, #defaults to 0 nacltask_cleanup_manager => $CleanupObj, %other_options ); or $qtree_obj->modify(%other_options); (Class method) Modify attributes of a Qtree. Also verify that all attributes got set to the specified values if C is set to 1. =over =item C<< nacltask_verify => 0|1 >> (Optional, defaults to 0) Verifies whether all of the attributes got set to the specified value. Note that it is also possible to perform the verification later by calling L. This can be used for cases where ONTAP chooses to not set all attributes to the exact values specified. In the call we can specify those attributes we want verified. =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 use for registering Default : will use the default cleanup manager. =item other options See L for all the other options accepted by this method. =back =cut sub modify { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my %opts = $pkg->_common_validate_with( params => \@args, additional_spec => { nacltask_verify => { type => SCALAR, default => 0 }, $pkg->_cleanup_validate_spec(), }, allow_extra => 1, ); my ($opts_for_register,$register_for_cleanup,%opts_for_cleanup); $pkg->_move_common_cleanup_opts( source => \%opts, target => \%opts_for_cleanup, nacltask_to_cleanup => \$register_for_cleanup, ); if ( $register_for_cleanup ){ $opts_for_register = $pkg->_common_modify_for_cleanup(%opts_for_cleanup, %opts); } $pkg->_call_method_then_verify(%opts); #Register for cleanup if $nacltask_to_cleanup is set. $pkg->_register_for_cleanup(%{$opts_for_register}) if ($register_for_cleanup); $Log->exit() if $may_exit; } =head2 purge NACL::STask::Qtree->purge( command_interface => $command_interface, qtree => $qtree_name, volume => $volume_name, vserver => $vserver_name, nacltask_wait => 1, #default 1 nacltask_if_purged => $action, #default "die" ); or $qtree_obj->purge(); (Class or instance method) This method deletes a qtree on the given volume and vserver. It also waits for the entry in the qtree table to be removed before returning (this can be configured using the C option) =over =item Options =over =item C<< nacltask_wait => 0|1 >> (Optional, defaults to 1) If 1 (the default), wait for this Qtree to be deleted (calls wait_for_deletion method). If 0, return immediately after the "delete" command has completed. =item C<< _was_deleted => \$scalar >> (Optional) When this option is provided a reference to a scalar variable, the variable gets filled in with a boolean value describing whether the qtree did not exist (value will be 0; this scenario is possible when nacltask_if_purged => "pass") or whether the qtree was deleted (value will be 1). =item C<< nacltask_if_purged=> 'die' | 'pass' >> (Optional, defaults to 'die') If "die", exception is thrown when qtree to be deleted does not exist. If "pass", exits quietly when qtree to be deleted does not exist. =item C<< polling_interval => $interval_in_seconds >> (Optional) Specifies the interval between polling for whether the qtree entry still exists in the table. (only relevant if nacltask_wait is set to 1) =back =over =item Exceptions =over =item C This type of exception is thrown when an attempt is made to delete a qtree that does not exists. =item C This type of exception is thrown when verification fails for the deleted qtree. =back =cut sub purge { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { nacltask_wait => { type => BOOLEAN, default => 1 }, # if_purged kept for backwards compatibility if_purged => { type => SCALAR, optional => 1, callbacks => $pkg_or_obj ->_if_action_completed_already_validate_callback( ) }, nacltask_if_purged => { type => SCALAR, optional => 1, callbacks => $pkg_or_obj ->_if_action_completed_already_validate_callback( ) }, _was_deleted => { type => SCALARREF, optional => 1 }, }, allow_extra => 1, ); my $command_interface = $opts{command_interface}; my $wait = delete $opts{nacltask_wait}; my %common_opts; $pkg_or_obj->_copy_common_component_params( source => \%opts, target => \%common_opts ); # nacltask_if_purged has highest precedence, then if_purged. If neither # is provided, default to "die" my $if_purged = delete $opts{nacltask_if_purged} || $opts{if_purged} || 'die'; delete $opts{if_purged}; my $pass = undef; my $job_ref; $opts{job_component} ||= \$job_ref; my $was_deleted = delete $opts{_was_deleted}; $$was_deleted = 0; # Call the component delete method blindly # if the qtree does not exist or is in a snapmirror relationship # then it would throw an exeception which is handled below. REDO: try { # Explicitly set "force" to true so that the delete command will # delete qtrees with files under it. CMode defaults force to true # but 7Mode does not, so we explicitly set it to force. $pkg_or_obj->delete( %opts, force => 'true' ); $$was_deleted = 1; # wait_for_deletion() (below) expects $opts{job_component} # to be a NACL::C::Job, not a reference. Hence the conversion. my $job_reference = delete $opts{job_component}; if ( ${$job_reference} ) { $opts{job_component} = ${$job_reference}; if (ref $pkg_or_obj) { # also store captured NACL::C::Job in the object $pkg_or_obj->job_component( ${$job_reference} ); } } } catch NACL::C::Exceptions::VolumeQtree::DoesNotExist with { my $exception =shift; $Log->comment("Qtree does not exist."); if ( $if_purged =~ /die/ ) { $exception->throw(); } elsif ( $if_purged =~ /pass/ ) { $pass = 1; } } catch NACL::C::Exceptions::VolumeQtree::QtreeContainsLuns with { my @luns = NACL::STask::Lun->find( %common_opts, command_interface => $command_interface, filter => { qtree => $opts{qtree}, volume => $opts{volume}, vserver => $opts{vserver}, }, ); map {$_->purge(%common_opts)} @luns; goto REDO; }; # If the qtree does not exist and the if_purged is set to pass # then we return quitely. if ($pass) { $Log->exit() if $may_exit; return; } # If wait flag is set, then we wait untill qtree is deleted if ($wait) { my %opts_for_wait = %opts; delete $opts_for_wait{'foreground'}; delete $opts_for_wait{'force'}; delete $opts_for_wait{'qtree-path'}; $pkg_or_obj->wait_for_deletion(%opts_for_wait); } $Log->exit() if $may_exit; } =head2 wait_for_deletion $qtree_obj->wait_for_deletion( "method-timeout" => $timeout, ) or NACL::STask::Qtree->wait_for_deletion( "method-timeout" => $timeout, command_interface => $command_interface, job => $job_component, qtree => $qtree, volume => $volume_name, vserver => $vserver_name, ); Wait for the qtree deletion to complete. If a job for deleting this qtree is known (because the mode is C-mode and NACL::STask::Qtree->purge was used to delete this object, or because it was passed in using the "job" option) then this waits for the job. =over =item Options =over =item C<< method-timeout => $timeout >> Optional for class method, Not Applicable for instance method) A NACL::C::Job object representing the C-mode Job used to delete this qtree. =item C<< job_component => $job_component >> Optional for class method, Not Applicable for instance method) A NACL::C::Job object representing the C-mode Job used to delete this qtree. =back =back =cut sub wait_for_deletion { $Log->enter() if $may_enter; # No need to validate here: we'll let the base class implementation of # wait_for_deletion do the validation for us. my ( $pkg_or_obj, %opts ) = @_; # Keeping backwards compatibility for the old "job" argument, even # though the supported (and documented) option is "job_component" $opts{job_component} ||= $pkg_or_obj->job_component() || $opts{job}; delete $opts{job}; $opts{'method-timeout'} ||= DEFAULT_TIMEOUT; $opts{polling_interval} ||= POLL_DELTA; # call the wait_for_deletion from the Component $pkg_or_obj->SUPER::wait_for_deletion(%opts); $Log->exit() if $may_exit; } =head2 rename NACL::STask::Qtree->rename( command_interface => $ci, volume => $volume, qtree => $qtree, vserver => $vserver, nacltask_verify => 1, #default '0' nacltask_to_cleanup => 1, nacltask_cleanup_manager => $cleanupobj, polling_interval => $polling_interval, ); or $Qtree_object->rename(...); This method is used to rename an existing qtree. And also provides an mechanism of cleanup for the objects that is renamed by this method. =over =item C<< nacltask_verify => 0|1 >> (Optional, defaults to 0) Verifies all of the attributes got set to the specified values. Note that it is also possible to perform the verification later by calling L. This can used for cases where ONTAP chooses to not set all attributes to the exact values specified. In the call we can specify those attributes we want to verify. =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(not to cleanup)) Flag indicating if this operation needs to be registered for clean up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering Default : will use the default cleanup manager. =item C<< method-timeout => $timeout >> Optional for class method, Not Applicable for instance method) A NACL::C::Job object representing the C-mode Job used to delete this qtree. =item C<< polling_interval=>$polling_interval >> (Optional) This is the interval (in seconds) after which the value of the attributes will be polled. If not specified it defaults to 20 seconds. See L for all the other options accepted by this method. =back =cut sub rename { $Log->enter() if $may_enter; my ( $pkg_or_obj, @args ) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { polling_interval => { type => SCALAR, default => 20 }, "method-timeout" => { type => SCALAR, default => 60 }, $pkg_or_obj->_cleanup_validate_spec(), newname => { type => SCALAR }, nacltask_verify => { type => SCALAR, default => 1 }, }, allow_extra => 1, ); my $command_interface = $opts{command_interface}; my $newname = $opts{newname}; my $vserver = $opts{vserver}; my $volume = $opts{volume}; my $nacltask_verify = delete $opts{nacltask_verify}; my %common_opts; my $timeout = delete $opts{"method-timeout"}; my $polling_interval = delete $opts{polling_interval}; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts, ); my ( %nacltask_options, $nacltask_to_cleanup, %opts_for_cleanup ); $pkg_or_obj->_copy_common_opts_for_cleanup( 'source' => \%opts, 'target' => \%opts_for_cleanup, 'nacltask_to_cleanup' => \$nacltask_to_cleanup, 'to_cleanup' => 'rename' ); $pkg_or_obj->SUPER::rename(%opts); #verifying the new-name my $end_time = time() + $timeout; FIND: { use warnings; try { my $vs_obj = $pkg_or_obj->find( command_interface => $command_interface, filter => { volume => $opts{volume}, vserver => $opts{vserver}, qtree => $newname, }, ); } catch NACL::Exceptions::NoElementsFound with { if ( time() > $end_time ) { $Log->exit() if $may_exit; NACL::APISet::Exceptions::TimeoutException->throw( "The rename has not happened within the TIMEOUT value = $end_time" ); } else { no warnings; Tharn::snooze($polling_interval); goto FIND; } } } if ($nacltask_to_cleanup) { $opts_for_cleanup{'new_opts'} = { 'newname' => $opts{'newname'} }; $opts_for_cleanup{'orig_opts'} = { 'newname' => $opts{'qtree'} }; $opts_for_cleanup{'qtree'} = $opts{'newname'}; $pkg_or_obj->_register_for_cleanup(%opts_for_cleanup); } $Log->exit() if $may_exit; } =head2 parents @parents = $qtree_obj->parents(); ( Class or Instance method ) This method returns the list containing the names of the parent objects of this resource. Used for finding dependencies between resources. =cut sub parents { $Log->enter() if $may_enter; $Log->exit() if $may_exit; return qw(NACL::STask::Volume); } 1;