# # Copyright (c) 2001-2016 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary Volume Task Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here package NACL::STask::Volume; use strict; use warnings; use feature 'state'; use POSIX; use base qw(NACL::C::Volume NACL::STask::STask); use Tharn qw( sleep snooze); use NATE::Log qw(log_global); my $Log = log_global(); use Params::Validate qw(validate validate_with validate_pos :types); use Scalar::Util qw(blessed); use NATE::BaseException qw(:try); use NACL::C::Exceptions::Volume::UnsupportedUnmount; use NACL::C::Exceptions::Volume::UnmountRoot (); use NATE::Exceptions::Argument (); use NACL::ComponentUtils qw(_optional_scalars); use NACL::C::Exceptions::Volume::DoesNotExist; use NACL::Exceptions::VerifyFailure; use NACL::Exceptions::Timeout; use NACL::STask::VolumeSnapshot; use NACL::CS::VolumeSnapshot; use NACL::MTask::Set::SnapshotListCompare; use NACL::ComponentUtils qw(Dumper); use NACL::CS::ClusterIdentity; use NACL::STask::NetworkInterface; use NACL::C::Snapshot; use NACL::C::SnapshotPolicy; use NACL::CS::StorageAggregate; use NACL::STask::Vserver; use NACL::STask::VolumeFile; use NACL::STask::VolumeEfficiency; use NACL::C::VolumeRecoveryQueue; use NACL::C::ApplicationVolume; #use NACL::STask::VolumeEncryption; use NACL::C::Exceptions::Volume::AlreadyExists (); use NACL::STask::Exceptions::NoAggregatesWithSufficientSpaceForVolume (); use NACL::STask::Exceptions::NoVserversWithSuitableAggrList (); use NACL::STask::Exceptions::NoVserversWithSuitableAllowedProtocols (); use NACL::STask::Exceptions::NoInterfacesWithSuitableAllowedProtocols (); use NACL::C::Exceptions::Volume::FailedToDeleteJunction (); use NACL::STask::Exceptions::SpaceComparisonFailure (); use NACL::C::Exceptions::Volume::VserverNotInAggrList (); use NACL::C::Exceptions::Volume::ConstituentsInInconsistentState (); use NACL::C::Exceptions::Volume::OperationNotPermittedOnDrVolume (); use NACL::C::Exceptions::Volume::ApplicationVolumeRemove (); use NATE::ParamSet qw(param_global); use NACL::CS::SnaplockComplianceClock; use NACL::C::VolumeSnaplock; use NACL::CS::Metrocluster; use Class::MethodMaker [ 'scalar' => [{-type => 'NACL::C::Job'}, 'job_component'], ]; # Same routine for aggregates and volumes. Pull implementation from # _AggregateVolume.pm. use NACL::STask::_AggregateVolume qw(:all); use NACL::STask::Mkfile; use NACL::GeneralUtils qw(nacl_method_retry get_random_hash); use List::MoreUtils qw(each_arrayref uniq); =head1 NAME NACL::STask::Volume =head1 DESCRIPTION C provides a number of well-defined but potentially complex or multi-step methods related to Volumes 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 Volume-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', 'online', 'offline', 'restrict', 'mount', shared_create, recyclable_create. Cleanup methods are, Volume Method Cleanup Method -------------------------------------- create purge modify modify online modify offline modify restrict modify rename rename (TBD) mount unmount shared_create shared_undo recyclable_create recyclable_undo =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 last job that operated on this volume. At the moment this will only get filled in for the job used to create this aggregate (if this task object was used to create this volume, and if C-mode was used so that there is/was a job ID) =head2 last_job B. The C attribute should be used instead, this has been retained for backwards compatibility. =head1 Linked tasks (See http://wikid.netapp.com/w/QA/projects/Libraries_Initiative/Project/Common_Infrastructure/Users_Guide/Component_Layer_Users_Guide#Linked_components for a more detailed explanation on linked components/tasks) This task is linked to the following tasks: =begin html
Prefix Linked Task
efficiency VolumeEfficiency
snapshot VolumeSnapshot
move VolumeMove
=end html Examples: # Invoking the "start" method of the VolumeEfficiency task $vol_obj->efficiency_start(%opts); # Invoking the "create" method of the VolumeSnapshot task my $snapshot_obj = $vol_obj->snapshot_create(snapshot => $snap_name, %opts); =head2 Linkage to VolumeFlexcache task Functionality of the L task can be accessed by using the C prefix. The C attribute of the volume object is used as the value of the C argument for the Flexcache methods. Examples: my $cache_obj = $vol_obj->flexcache_create(); my @caches = $vol_obj->flexcache_find(); =head1 METHODS =head2 create my $volume = NACL::STask::Volume->create( command_interface => $command_interface, volume => $volume, vserver => $vserver, nacltask_if_exists => $action, # default 'die' nacltask_wait => $boolean, # default '1' nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_verify => 1, # default 0 %other_options ); (Class method) Create an volume. This method provides additional services beyond what's inherent to the product's volume creation commands, as controlled and described by the new C, C and C options. It also mounts the volume (i.e. automatically assigns a junction-path for it) even if the C argument has not been provided. (For 7Mode it defaults to exporting the volume. This behavior can be disabled by setting 'junction-path' to undef.) Note that the arguments C, C, C and C are optional for this method. Refer to the section "Arguments defaulted/auto-determined" below for a description of the values auto-assigned to these arguments if they are not supplied by the caller. With these defaults, it is now possible to create a flex-volume by just doing: my $vol_obj = NACL::STask::Volume->create(command_interface => $node); A FlexGroup (FG) can be created by doing: my $fg_vol_obj = NACL::STask::Volume->create( command_interface => $node, nacltask_fg_constituent_count => $num_constituents, ); For the Snaplock-related support provided by this task, refer to the NACL_SNAPLOCK_* parameters under the C section below. =over =item Options =over =item C<< if_exists => $action >> B, use C instead. (This has been retained only for backwards compatibility). =item C<< nacltask_if_exists=>$action >> (Optional) What to do if the volume to be created already exists. If $action is "die" (the default), then fail with an exception (in the same way that the volume component would have: by trying the volume create and letting the product complain about the volume already existing). If action is "reuse", then do nothing (return a task object referring to the existing volume). If action is "purge", then delete the volume (see the C method, below) before creating a new one. =item C<< nacltask_wait => 0|1 >> (Optional) If 1 (the default), wait for this volume to be created (see "wait_for_creation" method, below). If 0, do not wait any longer than necessary (any longer than the volume creation command waits). =item C<< polling_interval => $interval >> (Optional) If nacltask_wait is set to 1, this is the interval at which to show the progress of the job (if we're watching the progress of the job) or the interval at which to poll the filer for the value of the "state" field. =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 $vol_obj = NACL::STask::Volume->create( if_exists => 'reuse', _was_created => \$was_created, %other_opts ); # Operate on $vol_obj here # ... # Now determine whether to clean up the volume, since we're not sure # whether we reused an existing volume or created a new one if ($was_created) { # New volume was created. Clean it up. $vol_obj->purge(); } =item C<< nacltask_storage_efficiency => $option >> (Optional) If nacltask_storage_efficiency is set to '1', it will enable and start the volume efficiency. if user does not pass this option, it keeps off the the volume efficiency option. Default - 0 =item C<< nacltask_destroy_corrupt_volume => 0|1 >> (Optional defaults to 0) If 7-Mode CLI command is used to delete the inconsistent volume,filer throws the prompt 'This volume is marked corrupt and could be useful for debugging purposes, are you sure you want to proceed? ' and asks for user confirmation. Set nacltask_destroy_corrupt_volume to 1 to delete inconsistent volume. This option is not applicable and ignored for 7-Mode ZAPI and C-Mode CLI/ZAPI. =item C<< nacltask_rng_args => { args_for_rng_here } >> (Optional, used only if a random volume name is generated) If no volume name is supplied, a random name is generated using L with two random words in it and with the suffix being "_vol". This argument can be used to customize the arguments to that method. For example, if we'd like to specify that the random generated volume name should be 255 characters length, then this could be: NACL::STask::Volume->create( %other_opts, nacltask_rng_args => { length => '255' } ); See L for a description of the arguments it accepts. =item C<< nacltask_choose_vserver_with_lif => 0 | 1 >> (Optional, defaults to 1, applicable only if a vserver is chosen by the method) If the C argument is not provided, then this method chooses one (see the section "Arguments defaulted/auto-determined" for how this is done). If this argument is set to 1, then the chosen vserver should have a data-lif associated with it. =item C<< is_infinivol => 0 | 1 >> (Optional, defaults to 0) Specifies whether the created volume is an infinivol or not. By default when the C argument is not provided, an aggregate that can be used is auto-determined. However, since infinivols are actually spread across multiple aggregates, this auto-determination of aggregate should not be done for infinivols. This argument specifies to the create() call that the auto-determination of C should not be done. =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =item C<< nacltask_usable_protocols >> (Optional) If a call to L<< NACL::STask::Volume->create|lib-NACL-STask-Volume-pm/create >> is made without the C argument, then the method attempts to find a vserver which can host the volume. When this options is specified, it considers only those vservers whose allowed-protocols supports the requested protocols and that vserver has set of interfaces which supports the requested protocols. Ex: when you say, my $volume = NACL::STask::Volume->create( "command_interface" => $ci, 'nacltask_usable_protocols' => ['nfs', 'cifs'] ); It will try to find the vservers which supports both "nfs" and "cifs" protocols and should have the interfaces which adds up to the requested protocols (in above case 'nfs' and 'cifs'). Some of the valid protocols are, nfs, cifs, fcp, iscsi, ndmp. =item C<< nacltask_verify=>$boolean >> (Optional) Verify the created volume attributes from C with the inputs provided to the task.Throw a exception when there is a difference in input and C state attributes. =item C<< "method-timeout" => $timeout >> (Optional) As component method-timeout, controls how long the command will wait before completing. However, the default timeout has been raised to 7200 seconds. =item C The options accepted for MCC configuration replication verification is documented at L. =item C<< nacltask_randomize_attributes => 0|1 >> (Optional, defaults to 0) Flag indicating whether to create a volume with randomized attributes. =item C<< nacltask_attributes => $hashref >> (Optional) This option takes in effect when nacltask_randomize_attributes is 1. The hash from which random attributes are selected for creating the volume. The hash defaults to: { 'security-style' => [qw(unix mixed ntfs)], 'space-guarantee' => [qw(none volume )], 'nvfail' => [qw(on off)], } =item C<< partner_command_interface => $ci >> (Optional) This parameter contains the command interface of the partner cluster in the mcc configuration. This will the partner of the node which is passed to command_interface parameter. If the value is not passed the partner node is found using "metrocluster node show" command. If passed it will be of NACL::C::Node type of object. =item C<< nacltask_fg_constituent_count => $count >> This option can be used to specify the number of constituents volumes for the FlexGroup. When this option is specified, the task auto-determines the value of C. =item C<< candidate_aggregates => \@aggr_objs >> Restrict the method to only create the volume on one of the aggregates specified. This is especially useful if it is necessary to limit where the volume gets created. Examples: =over =item Create on an aggregate which is on a particular node my @aggrs_on_some_node = NACL::STask::Aggregate->find( command_interface => $node, filter => {node => $some_node} ); NACL::STask::Volume->create( command_interface => $node, candidate_aggregates => \@aggrs_on_some_node ); =item Create on a hybrid aggregate my @hybrid_aggrs = NACL::STask::Aggregate->find( command_interface => $node, filter => {hybrid => 'true'} ); NACL::STask::Volume->create( command_interface => $node, candidate_aggregates => \@hybrid_aggrs ); =back =item << candidate_vservers => \@vserver_objs >> Restrict the method to only create the volume on one of the aggregates specified. This is especially useful if it is necessary to limit where the volume gets created. Examples: =over =item Create volume on a particular IPSpace my @vs_on_ipspace = NACL::STask::Vserver->find( command_interface => $node, filter => {ipspace => $ipspace} ); NACL::STask::Volume->create( command_interface => $node, candidate_vservers => \@vs_on_ipspace ); =item Create volume on a vserver with NFS 4.0 enabled my @vs_nfs = NACL::STask::VserverNfs->find( command_interface => $node, filter => {'v4.0' => 'enabled'} ); my @vservers_with_v4 = map {$_->emerge_contained_object(type => 'NACL::STask::Vserver')} @vs_nfs; NACL::STask::Volume->create( command_interface => $node, candidate_vservers => \@vservers_with_v4 ); =back =item command_interface, apiset_must, apiset_should, method-timeout, volume, diskcount, disklist, voltype, etc. All of the other various options to L<< NACL::C::Volume->create | lib-NACL-C-Volume-pm/create >> =back =back =over =item Arguments defaulted/auto-determined =over =item C Note that if this is not specified, a random name is generated using L. (See C for how to customize arguments to random_name_generator()) =item C In 7Mode, the "size" is defaulted to "20M". (CMode ONTAP defaults the value of "size" to 20M if not provided) =item C (Note that the C argument is inapplicable for infinivols and flexgroups, so the aggregate value is not determined if the vserver is determined to be a repository-vserver, or if the C argument is provided Also, if a vserver command_interface is used, then an appropriate aggregate cannot be determined, because it's not possible to run the aggregate commands in vserver context) In 7Mode, an online aggregate is chosen that has sufficient available space to host the volume. (For example, if a volume of 500MB needs to be created, an aggregate with more than 500MB of available space is chosen) In CMode, the aggregate chosen additionally needs to satisfy the aggr-list criteria of the vserver. The "aggr-list" of a vserver essentially describes the aggregates which are accessible through that vserver. Hence, the aggregate chosen must be present in the "aggr-list" of the vserver chosen/provided. If there are no aggregates with sufficient space available to host the volume, then a L is thrown. If there are no aggregates found satisfying the vserver aggr-list restrictions then a L exception is thrown. =item C This is auto-determined when C is specified. =item C This is only applicable when creating a FlexGroup. If aggr-list-multiplier exists and is defined, the passed in value it used and each aggregate in the aggr-list is mutliplied by the given value. If aggr-list-multiplier exists and is undef, the default ONTAP value of 4 is used. If aggr-list-multiplier neither exists nor is defined, we use 1 as the aggr-list-multiplier we pass to ONTAP. =item C It automatically select aggregates for FlexGroup Constituents. If this is defined then aggr-list, aggregate or aggr-list-multiplier cannot be used with it. =item C (The auto-determination of vservers is currently not supported if the C argument is provided) In CMode, if the vserver argument is not provided, a data-vserver is searched for and the one matching the aggr-list restriction of the aggregate chosen/provided is the one that's chosen. It's possible to limit the vserver chosen to one that has a data-lif associated it by using the C argument. If there is no suitable aggregate/vserver combination satisifying the aggr-list requirements, then a L exception is thrown. =item C If the junction-path argument has not been provided, then this method automatically chooses a junction-path (currently is is /$volname) and mounts it. Note that this is done only if: * The test is being run on a CMode system * The volume being created is not a constituent volume. (constituent volumes cannot be mounted. It is assumed that the argument C is required for the creation of a constituent volume and the absence of it indicates the volume being created is not a constituent volume) * The volume is of type "RW". Note that if C is not provided, then ONTAP defaults it to "RW". (Volumes of type "DP" and "DC" can be mounted, but they cannot be mounted at creation time since they require certain other steps to be performed before they can be mounted) If we do not want the method to auto-assign a junction-path, then a value of undef should be passed for this argument. NACL::STask::Volume->create(%other_opts, 'junction-path' => undef); =back =item Parameters The following command-line parameters can be specified to affect the behaviour of the call at run-time. =over =item NACL_SNAPLOCK_AUTOCOMMIT_PERIOD The time taken for a regular file since it's last modification time to be commited to WORM automatically. =item NACL_SNAPLOCK_DEFAULT_PERIOD This value tells what the default rentention period will be after a file is set to WORM. When NACL_SNAPLOCK_TYPE is set, this is defaulted to 60s. This also accepts a special value of "default". When specified as "default", the task will automatically pick a value (which is currently 5s). =item NACL_SNAPLOCK_MINIMUM_PERIOD This value tells the default minimum retention period a file can be set to in a Snaplock Volume. =item NACL_SNAPLOCK_MAXIMUM_PERIOD This value tells the default maximum retention period a file can be set to in a Snaplock Volume. =item NACL_SNAPLOCK_NUM_FILES This value tells the number of files created on a snaplock enabled volume. If any files are created, then their fingerprint will be displayed. The file fingerprint for each file will have markers around it to ease obtaining from existing log files. The markers will be of the form: #===== BEGIN SNAPLOCK FINGERPRINT (path=) ====== #... output of commands here ... #===== END SNAPLOCK FINGERPRINT (path=) ===== # =item NACL_CONTAINER_TYPE This value tells the default type of volume to be created: either FlexVol or FlexGroup. Specific calls to this command can override this parameter if a specific volume type can be infered from the input parameters. Valid values are (case-insensitive): fv, flexvol, fg, flexgroup This param cannot be used to create Infinitevols. =back =back =over =item Exceptions =over =item C This type of exception is thrown when an attempt is made to create a volume that already exists. =item C This type of exception is thrown when dynamic discovery of the vserver is not possible for infinivols. =item C This type of exception is thrown when there are no vserver with suitable aggrlist. =item C This type of exception is thrown when there is a failure to find a suitable data-vserver to host the volume. =item C This type of exception is thrown when nacltask_usable_protocols is specified and there are no interfaces found in a vserver which supports the specified protocols through nacltask_usable_protocols. =item C This type of exception is thrown when nacltask_usable_protocols is specified and there are no vservers found which supports the specified protocols through nacltask_usable_protocols. =back =back =cut sub create { $Log->enter(); my ($pkg, @args) = @_; # We cannot directly use _if_exists_validate_spec since it defaults # the value to "die". This would mean that we would not know whether # if_exists or nacltask_if_exists was provided. my %opts = $pkg->_common_validate_with( params => \@args, 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_wait => {type => SCALAR, default => 1}, nacltask_storage_efficiency => {type => SCALAR, default => 0}, nacltask_destroy_corrupt_volume => {type => BOOLEAN, default => 0}, _was_created => {type => SCALARREF, optional => 1}, _optional_scalars(qw(volume)), nacltask_rng_args => $pkg->_rng_args_validate_spec(), nacltask_choose_vserver_with_lif => { type => BOOLEAN, default => 1 }, nacltask_is_infinivol => { type => BOOLEAN, optional => 1 }, $pkg->_cleanup_validate_spec(), # Since 'nfs' is most widely used protocols, it is defaulted to 'nfs' nacltask_usable_protocols => { type => ARRAYREF, default => ['nfs'] }, nacltask_attributes => $pkg->_random_vol_attr_spec(), nacltask_randomize_attributes => { type => SCALAR, default => 0 }, candidate_aggregates => {type => ARRAYREF, default => []}, candidate_vservers => {type => ARRAYREF, default => []}, nacltask_fg_constituent_count => {type => SCALAR, optional => 1}, # This is a parameter for the Component, but we're validating it # because we use it here in the STask 'aggr-list' => {type => ARRAYREF, optional => 1}, $pkg->_mcc_validate_spec(), nacltask_verify => { type => BOOLEAN, default => 0 }, }, allow_extra => 1, ); if(defined $opts{'auto-select-aggregates'}){ if((defined $opts{'aggregate'}) || (defined $opts{'aggr-list'}) || (defined $opts{'auto-list-multiplier'})){ $Log->warn("It is not recommended to pass aggregate, aggr-list, auto-list-multiplier" ." when auto-select-sggregates is used"); } } ###################### Section 1 ################################### # Extract values from %opts and sub-divide them into separate hashes # ###################################################################### # move the cleanup relate options to seperate hash my ( %opts_for_cleanup, %opts_for_register, $nacltask_to_cleanup ); my %mcc_opts; $pkg->_move_common_mcc_opts( source => \%opts, target => \%mcc_opts, ); $pkg->_move_common_cleanup_opts( source => \%opts, target => \%opts_for_cleanup, ); # hash to store additional options for purge() method my %purge_opts = (); # additional option required for purge $purge_opts{nacltask_destroy_corrupt_volume} = delete $opts{nacltask_destroy_corrupt_volume}; my $usable_protocols = delete $opts{'nacltask_usable_protocols'}; # Transform %opts from the options we received into the options to # pass to the base class method. my $if_exists = delete $opts{nacltask_if_exists} || $opts{if_exists}; my $nacltask_storage_efficiency = delete $opts{nacltask_storage_efficiency}; # Having the "delete $opts{if_exists}" on the previous line would not # necessarily delete it, because it would not get evaluated if # the "delete $opts{nacltask_if_exists}" returned some value. Hence, # we're explicitly deleting it here separately. delete $opts{if_exists}; # Assign the default of "die" if neither of nacltask_if_exists or if_exists # were provided. $if_exists ||= 'die'; my $wait = delete $opts{nacltask_wait}; my $was_created = delete $opts{_was_created}; $$was_created = 0; my $nacltask_verify = delete $opts{nacltask_verify}; my $rng_args = delete $opts{nacltask_rng_args}; my $choose_vserver_with_lif = delete $opts{nacltask_choose_vserver_with_lif}; my $is_infinivol = delete $opts{nacltask_is_infinivol} || $opts{is_infinivol}; my $candidate_aggrs = delete $opts{candidate_aggregates}; my $candidate_vservers = delete $opts{candidate_vservers}; my $command_interface = $opts{command_interface}; my $ci_is_cmode = $command_interface->is_cmode(); my $is_tradvol = defined $opts{disklist} || defined $opts{diskcount}; my $is_constituent = defined $opts{'constituent-role'}; my $is_vsroot = defined $opts{vsroot} && $opts{vsroot} eq 'true'; my $fg_const_count = delete $opts{nacltask_fg_constituent_count}; if (!defined $fg_const_count && defined $opts{'aggr-list'}) { $fg_const_count = scalar @{$opts{'aggr-list'}}; } my $is_fg; if (!defined($opts{aggregate}) and !defined($opts{'aggr-list'}) and !defined($fg_const_count)) { my $options = NATE::ParamSet->new(global => 1); $options->parameters( definitions => [ { name => 'NACL_CONTAINER_TYPE', spec => 's', default => '', description => 'Type of volume to create by default. ' . 'Can be any one of fg, fv, flexgroup, flexvol (case insensitive).', } ] ); $options->process; my $container_type = $options->get('NACL_CONTAINER_TYPE'); if ($container_type) { if ($container_type =~ /^fg$|^flexgroup$/i) { $is_fg = 1; $is_infinivol = 0; $fg_const_count = 4; } elsif ($container_type =~ /^fv$|^flexvol$/i) { $is_fg = 0; $is_infinivol = 0; } else { # If unspecified, treat it as FlexVol by default $is_fg = 0; $is_infinivol = 0; } } } # If either aggr-list or nacltask_fg_constituent_count is provided, then # we want to create a FlexGroup $is_fg = defined ($opts{'aggr-list'}) || defined $fg_const_count; # If we want to create an FG, then is_infinivol has to be 0. $is_infinivol //= 0 if ($is_fg); my %common_opts_with_ci; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_with_ci, ); my %common_opts = %common_opts_with_ci; delete $common_opts{command_interface}; my %wait_for_creation_opts = %common_opts; $pkg->_hash_move( source => \%opts, target => \%wait_for_creation_opts, move => [qw(polling_interval)], ); $pkg->_hash_copy( source => \%opts, target => \%wait_for_creation_opts, copy => [qw(state)], ); unless ($ci_is_cmode) { # In 7Mode we need to wait for the volume to come online before # we can modify it to any other state. $wait_for_creation_opts{state} = 'online'; } my $self; ###################### Section 2 ################################### # Auto-assign/determine values here # ###################################################################### # We want to know the job ID of the job performed by # SUPER::create, whether or not our caller does. my $dummy; $opts{job_component} ||= \$dummy; my $is_infinivol_ref; $opts{is_infinivol_ref} ||= \$is_infinivol_ref; $opts{'method-timeout'} ||= AGGREGATE_TIMEOUT; my $size_per_vol; if (defined $opts{size}) { my $size_bytes = $pkg->convert_to_bytes_ignore_exception($opts{size}); if ($is_fg) { # "size" is total size of FG, so size per constituent is total # size / number of constituents $size_per_vol = $size_bytes/$fg_const_count; } else { $size_per_vol = $size_bytes; } } else { if ($is_fg) { # Default to 20m. $size_per_vol = 20 * 1024 * 1024; } elsif (!$ci_is_cmode && !$is_tradvol) { # "size" is a mandatory argument for volume creation (for 7Mode). # CMode ONTAP defaults it (currently to 20M) if not provided. # For 7Mode, we assign the default in the library here to be # 20M to match the current CMode default. (We do this only if not # a tradvol.) # We leave this value untouched in CMode because it's best to # let ONTAP assign the default. $opts{size} = '20m'; } } # This is an array-reference of "suitable" aggregates. If the user # had supplied one in the call, we use that (in this case, this array will # have a single value - the aggregate supplied by the user). # If the aggregate name is not supplied by the caller, then we find # aggregates with sufficient size to host the aggregate and the # array will contain these aggregates in descending order of available # size (i.e. from aggregates with most available size to least available # size). my @suitable_aggregates; # If aggregates need to be determined, this will hold the list of CS # objects for the suitable aggregates. Each object will have the availsize # set. my @suitable_aggrs_cs; # Will be filled later; is a hash-ref, with key being aggregate name, # value being array with number of entries being the number of # constituents it can hold. For example, if aggr1 can hold 2 constituents # and aggr2 can hold 3, then the DS will be of the form: # { aggr1 => ['aggr1', 'aggr1',], aggr2 => ['aggr2', 'aggr2', 'aggr2'] } my $suitable_aggrs_ds = {}; my $construct_suitable_aggrs_ds = sub { # Already been defined. return if (keys %$suitable_aggrs_ds); # This is the size of each FG constituent foreach my $aggr_cs (@suitable_aggrs_cs) { my $aggr_name = $aggr_cs->aggregate(); my $availsize = $aggr_cs->availsize(); my $num_const_aggr_can_hold = int ($availsize / $size_per_vol); $suitable_aggrs_ds->{$aggr_name} = [($aggr_name) x $num_const_aggr_can_hold]; } }; my $form_aggr_intersection = sub { # Form an intersection between the vserver's aggr-list and the # @suitable_aggregates list. my @vs_aggr_list = @_; # Do NOT use List::Compare, or any of the other CPAN modules that # provide list/array comparison functionality, since they all # (currently, at least) use hashes internally, so they do not # maintain the order of the input lists. Here, we require # the intersection to be in the order of @suitable_aggregates, # since this is sorted by availsize, so we have our own logic # to form the intersection AND maintain order. # Originally discovered in burt 929984. my %vs_aggr_hash = map {$_ => 1} @vs_aggr_list; my @intersection; foreach my $suitable_aggr (@suitable_aggregates) { if (exists $vs_aggr_hash{$suitable_aggr}) { push @intersection, $suitable_aggr; } } return @intersection; }; # Given a list of aggregates, determine the value of "aggr-list". # (Used only for FGs) my $populate_group_aggr_list = sub { my @aggr_list_to_use = @_; # Copy out fg_const_count number of elements from the list of # aggregates provided. my $extract_from_list = sub { my @list = @_; # Array index begins from 0, so subtract one from count my $last_index = $fg_const_count - 1; my @new_list = @list[0 .. $last_index]; # Remove undef's from the list @new_list = grep {defined $_} @new_list; return @new_list; }; if (@aggr_list_to_use >= $fg_const_count) { return $extract_from_list->(@aggr_list_to_use); } else { $construct_suitable_aggrs_ds->(); # The number of aggregates is less than the number of # constituents required. $suitable_aggrs_ds has details of # how many constituents each aggregate can hold. Use that to # construct an aggr-list. my %aggrs_ds_subset; $pkg->_hash_copy( source => $suitable_aggrs_ds, target => \%aggrs_ds_subset, copy => \@aggr_list_to_use ); # Attempt to divide the constituents among all the aggregates # instead of putting them all on one aggregate. # Example: # Need 6 constituents; aggr1 can hold 10 constituents, # aggr2 can hold 4, aggr3 can hold 1. # Instead of putting all 6 constituents on aggr1, divide them # among all the aggregates. The eventual list determined would be # [aggr1, aggr2, aggr3, aggr1, aggr2, aggr1] my $ea = each_arrayref(values %aggrs_ds_subset); my @final_aggr_list; while (my @temp_arr = $ea->()) { push @final_aggr_list, @temp_arr; } return $extract_from_list->(@final_aggr_list); } }; if (defined $opts{aggregate}) { # The algorithm below to choose the vserver depends on this # being populated. Rather than having two separate code flows for # when $opts{aggregate} is defined and not, let's just populate # this to be the aggregate supplied by the caller. @suitable_aggregates = ($opts{aggregate}); } elsif (defined $opts{'aggr-list'}){ @suitable_aggregates = @{$opts{'aggr-list'}}; } else { # Neither aggregate nor aggr-list were provided, so we might need # to auto-determine it here. # Skip the auto-determination of aggregates if # 1. diskcount/disklist is provided (i.e. want to create a tradvol) OR .. goto AFTERAGGR if ($is_tradvol); # 2. Is an infinivol (CMode only) if ($ci_is_cmode) { unless (defined $is_infinivol) { if (defined $opts{vserver} || $opts{command_interface}->isa('NACL::C::Vserver')) { # Do this only if we know which vserver to filter on $is_infinivol = $pkg->_determine_if_infinivol_being_created(%opts); } else { # No vserver provided to filter on - assumption is that we # want to create a flexvol $is_infinivol = 0; } } $opts{is_infinivol} = $is_infinivol; goto AFTERAGGR if ($is_infinivol); } my %aggr_find_opts = %common_opts_with_ci; $pkg->_hash_copy_defined( source => \%opts, target => \%aggr_find_opts, copy => [qw(size vsroot space-guarantee)] ); @suitable_aggregates = $pkg->find_suitable_aggregates_for_volume( %aggr_find_opts, is_constituent => $is_constituent, candidate_aggregates => $candidate_aggrs, _get_cs_objs => \@suitable_aggrs_cs, size => $size_per_vol, ); if ($ci_is_cmode) { # For a FlexGroup, the number of aggregates required is specified # through "nacltask_fg_constituent_count" if ($is_fg) { if (@suitable_aggregates < $fg_const_count) { # The total number of "suitable aggregates" is lower than the # number of constituents. Let's now see, by looking at the # "availsize" value, how many constituents each aggregate can # hold, and whether this total matches the number of # constituents required. # Simple example: # * We want 4 constituents, each of size 20m # * 2 suitables aggregates are returned # ** First aggregate has 70MB free space # ** Second aggregate has 50MB free space # In this situation, the first aggregate can hold 3 # constituents, and the second aggregate can hold 2 # constituents. Hence, all constituents can be # created. $construct_suitable_aggrs_ds->(); my $total_const_can_be_held; while (my ($aggr, $arrayref) = each %$suitable_aggrs_ds) { $total_const_can_be_held += scalar @$arrayref; } if ($total_const_can_be_held < $fg_const_count) { # Create a new exception for this? $Log->exit(); NATE::BaseException->throw('The FlexGroup to be created ' . "requires space for $fg_const_count constituents, " . "but there is only space for $total_const_can_be_held"); } } } if (defined $opts{vserver}) { my $vs_cs = NACL::CS::Vserver->fetch( %common_opts_with_ci, filter => {vserver => $opts{vserver}}, requested_fields => ['aggr-list'] ); my @vs_aggr_list = $vs_cs->aggr_list(); if (!defined $vs_aggr_list[0] || $vs_aggr_list[0] eq '-') { # aggr-list of "-" means all aggregates are on the aggr # list of this vserver, so all aggregates are compatible # with this vserver. if ($is_fg) { $opts{'aggr-list'} = [$populate_group_aggr_list->(@suitable_aggregates)]; } else { $opts{aggregate} = $suitable_aggregates[0]; } } else { # aggr-list is not "-", so is a list. Form an # intersection between the vserver's aggr-list and # the "suitable aggregates" and store this in @aggr_intersection. my @aggr_intersection = $form_aggr_intersection->(@vs_aggr_list); if ($is_fg) { my @final_aggr_list = $populate_group_aggr_list->(@aggr_intersection); if (@final_aggr_list >= $fg_const_count) { $opts{'aggr-list'} = \@final_aggr_list; } else { $Log->exit(); NATE::BaseException->throw('The FlexGroup to be created ' . "requires space for $fg_const_count constituents, " . 'but there is only space for ' . scalar (@final_aggr_list) . ' constituents'); } } else { # Not a FlexGroup, so only a single aggregate needed. $opts{aggregate} = $aggr_intersection[0]; if (!defined $opts{aggregate}) { my $error = 'None of the suitable aggregates found '; my $aggrs_str = join ', ', @suitable_aggregates; $error .= "($aggrs_str) were in the aggr-list of " . 'the vserver provided by the caller (' . "$opts{vserver}). Aggr-list of the vserver:\n"; my $aggr_list_str = join ', ', @vs_aggr_list; $error .= "$aggr_list_str\n"; $Log->exit(); NACL::STask::Exceptions::NoVserversWithSuitableAggrList ->throw( text => $error, aggregates => \@suitable_aggregates, vserver_cs_objs => [$vs_cs] ); } } } } } else { # For 7Mode we can simply pick the first aggregate. For CMode, # the choice of aggregate will be constrained by vserver aggr-list # restrictions, so the value will be determined below $opts{aggregate} = $suitable_aggregates[0]; } } AFTERAGGR: ###### Attempt to pick vservers here ##### if ($ci_is_cmode && !defined $opts{vserver}) { my $repo_canned_filter; if ($is_infinivol) { $repo_canned_filter = 'repository_vservers'; } else { $repo_canned_filter = 'non_repository_vservers'; } # Determine the vserver which can be used by analyzing the # aggr-list of each of the vservers. If an aggregate is on the # my @vservers_cs; # Pick only running vservers. This is especially necessary for # MCC environments where the vservers on the remote site will not # be running. my $vs_filter = {'operational-state' => 'running'}; if (@$candidate_vservers) { my @vserver_names; foreach my $candidate_vs (@$candidate_vservers) { if (blessed ($candidate_vs)) { if ($candidate_vs->can('vserver')) { push @vserver_names, $candidate_vs->vserver(); } else { $Log->exit(); NATE::Exceptions::Argument->throw('All values ' . "provided through the 'candidate_vservers' " . 'option in the call to NACL::STask::Volume->create ' . 'should have been vserver objects, but one of ' . 'them was of type ' . ref $candidate_vs ); } } else { push @vserver_names, $candidate_vs; } } $vs_filter->{vserver} = join '|', @vserver_names; } try { @vservers_cs = NACL::CS::Vserver->fetch( %common_opts_with_ci, filter => $vs_filter, canned_filters => ['data_vservers', $repo_canned_filter], requested_fields => ['aggr-list', 'allowed-protocols', 'rootvolume'] ); } catch NACL::Exceptions::NoElementsFound with { my $exception = shift; $exception->set_text('NACL::STask::Volume::create attempted ' . 'to find a suitable data-vserver to host the volume but ' . "none could be found:\n" . $exception->text()); $Log->exit(); $exception->throw(); }; if ($choose_vserver_with_lif) { # Chosen vserver should have a data-lif, modify @vservers_cs # to only contain these vservers my @data_vservers; foreach my $vserver_cs (@vservers_cs) { push @data_vservers, $vserver_cs->vserver(); } my $vs_filter = join '|', @data_vservers; try { my @interfaces_objs = NACL::CS::NetworkInterface->fetch( %common_opts_with_ci, filter => {vserver => $vs_filter}, requested_fields => ['data-protocol'] ); # Update @vservers_cs to only contain entries for # those vservers found to have a data-lif my %interfaces_keyed_by_vserver; foreach my $interface_obj (@interfaces_objs) { $interfaces_keyed_by_vserver{$interface_obj->vserver} = 1; } my @new_vservers_cs_list; foreach my $vserver_cs (@vservers_cs) { if (exists $interfaces_keyed_by_vserver{$vserver_cs->vserver()}) { push @new_vservers_cs_list, $vserver_cs; } } @vservers_cs = @new_vservers_cs_list; if (@{$usable_protocols}) { $pkg->_validate_allowed_protocols_on_vservers_and_lif( 'vserver_states' => \@vservers_cs, 'interface_states' => \@interfaces_objs, 'requested_protocols' => $usable_protocols ); } } catch NACL::Exceptions::NoElementsFound with { my $exception = shift; my $data_vservers_list = join ', ', @data_vservers; $exception->set_text('Vserver chosen should have a ' . "data-lif (since 'nacltask_choose_vserver_with_lif' " . 'was set to 1) but none of the available data-vservers ' . "($data_vservers_list) has a data-lif associated " . "with it.\n" . $exception->text()); $Log->exit(); $exception->throw(); }; } foreach my $vserver_cs (@vservers_cs) { my $vs_name = $vserver_cs->vserver(); my $root_volume = NACL::CS::Volume->fetch( %common_opts_with_ci, filter => {volume => $vserver_cs->rootvolume()}, requested_fields => ['state'], ); # Skip the vserver if it's root volume state is either '-' or 'offline' next if ( $root_volume->state() eq '-' || $root_volume->state() eq 'offline'); my @vs_aggr_list = $vserver_cs->aggr_list(); if ($vs_aggr_list[0] eq '-') { # All aggregates are on the aggr-list of this vserver # This vserver can be chosen and we can choose any of # the aggregates, so let's choose the first one (i.e. # the one with the most available space) $opts{vserver} = $vs_name; if ($is_fg) { $opts{'aggr-list'} //= [$populate_group_aggr_list->(@suitable_aggregates)]; } else { if (!$is_infinivol) { $opts{aggregate} //= $suitable_aggregates[0]; } } last; } else { my @aggr_intersection = uniq($form_aggr_intersection->(@vs_aggr_list)); if ($is_fg) { if (defined $opts{'aggr-list'}) { # Check that all the aggregates specified are in the # aggr-list of this vserver. my @unique_vol_aggr_list = uniq(@{$opts{'aggr-list'}}); if (@unique_vol_aggr_list == @aggr_intersection) { $opts{vserver} = $vs_name; last; } } else { my @final_aggr_list = $populate_group_aggr_list->(@aggr_intersection); if (@final_aggr_list >= $fg_const_count) { $opts{'aggr-list'} //= \@final_aggr_list; $opts{vserver} = $vs_name; last; } } } else { if ($is_infinivol) { $opts{vserver} = $vs_name; last; } elsif (@aggr_intersection) { $opts{vserver} = $vs_name; $opts{aggregate} //= $aggr_intersection[0]; last; } } } } if ($ci_is_cmode && !defined $opts{vserver}) { # If the vserver value could still not be filled in, then we # need to throw an exception my $error = "No data-vservers found with suitable aggr-list\n"; my $suitable_aggrs = join ', ', @suitable_aggregates; $error .= "Suitable aggregates found: $suitable_aggrs\n"; $error .= 'The following are the data-vservers and their ' . "respective aggr-list:\n\n"; foreach my $cs (@vservers_cs) { $error .= "\tVserver: " . $cs->vserver() . "\n"; my @aggr_list = $cs->aggr_list(); my $aggr_list_str = join ', ', @aggr_list; $error .= "\tAggr-list: $aggr_list_str\n\n"; } $Log->exit(); NACL::STask::Exceptions::NoVserversWithSuitableAggrList->throw( text => $error, aggregates => \@suitable_aggregates, vserver_cs_objs => \@vservers_cs ); } } # Possible to create a constituent volume without specifying a name - # ONTAP picks a valid one in that case. Hence for constituent volumes, # do NOT auto-assign a name. # Keep this piece of code here because by this point we know the vserver # value as well as whether this is an infinivol or not my $random_name_generated; if (!defined $opts{volume} && !$is_constituent) { $random_name_generated = 1; if ($is_vsroot) { # For vserver root volumes let's name them as "_root" my $vserver = $opts{vserver} || $command_interface->vserver(); $opts{volume} = "${vserver}_root"; } elsif ($is_infinivol) { # Let's tag an infinite volume with a suffix of "infinivol" $opts{volume} = $pkg->random_name_generator(%$rng_args, suffix => 'infinivol'); } elsif ($is_fg) { # Let's tag a FlexGroup with a suffix of "fg" $opts{volume} = $pkg->random_name_generator(%$rng_args, suffix => 'fgvol'); } else { $opts{volume} = $pkg->random_name_generator(%$rng_args); } } # In CMode, mounting can be done at create time. In 7Mode, we need to run # an extra command (exportfs) to do so. $need_to_mount_7m has the value of # whether to mount (it should be "export", more correctly) in 7Mode. This # variable is ignored for CMode. # We default to making the 7Mode implementation mount (in keeping with # what we do for CMode). We switch this off if junction-path is sent as # undef, or if junction-active is set to false. # If CMode, then set to 0. my $need_to_mount_7m = !$ci_is_cmode; if (exists $opts{'junction-path'}) { if (!defined $opts{'junction-path'}) { # "junction-path" being sent as undef is meant to signify that the # STask should not auto-assign a junction-path. The underlying # component will fail if undef is passed, so we delete it from # the options sent through. delete $opts{'junction-path'}; $need_to_mount_7m = 0; } } else { # If "junction-path" has not been sent, then auto-assign one (for # CMode only). # However: # 1) If we're attempting to create a constituent volume (the # "constituent-role" option is necessary for this), then it cannot # be mounted. # 2) If type is not RW, the volume cannot be mounted at create time. # (Note that the default type is "RW", so if type has not been sent # then it is assumed to mean "RW") # 3) If the volume is a vserver root, then it cannot be mounted # 4) Infinivols choose their own junction-path, so we don't need to # auto-assign one if ( $ci_is_cmode && !$is_constituent && (!defined $opts{type} || $opts{type} eq 'RW') && !$is_vsroot && !$is_infinivol && (!defined $opts{state} || $opts{state} eq 'online')) { $opts{'junction-path'} = $pkg->_determine_junction_path(volume => $opts{volume}); } } # If a vserver root volume is being created, then the "security-style" # is mandatory. We default it to "mixed". if (!$opts{'security-style'} && $is_vsroot) { $opts{'security-style'} = 'mixed' } if (defined $opts{'junction-active'} && $opts{'junction-active'} eq 'false') { $need_to_mount_7m = 0; } $need_to_mount_7m = 0 if ($command_interface->version_manager()->is_sksim()); my $need_to_modify_state_7m = $pkg->_determine_if_need_to_modify_state_7m( state => $opts{state}, is_cmode => $ci_is_cmode, ); ###################### Section 3 ################################### # Invoke component and do post-processing # ###################################################################### CREATE: { use warnings qw(exiting); if ($is_fg && !exists $opts{'aggr-list-multiplier'}) { $opts{'aggr-list-multiplier'} = "1"; } # We want to allow using the default aggr-list-multiplier form ONTAP (4) if # aggr-list-multiplier is passed in as undef. In that case, we delete it # so that we get the ONTAP default. delete $opts{'aggr-list-multiplier'} if (!defined $opts{'aggr-list-multiplier'}); # Common exception handler for AggregateRequired and # InvalidDataConstituent exceptions my $handle_exception = sub { # Checks if the volume exists and if so, performs the if_exists # handler my %opts_in_sub = validate_with( params => \@_, spec => { exception => { type => OBJECT, isa => 'NACL::APISet::Exceptions::ResponseException' }, is_infinivol => { type => BOOLEAN }, } ); my $exception = $opts_in_sub{exception}; my $is_infinivol = $opts_in_sub{is_infinivol}; my %filter; $pkg->_copy_primary_keys( source => \%opts, target => \%filter ); if ($is_infinivol) { $filter{'volume-style'} = 'infinitevol'; } else { $filter{'volume-style'} = 'flex'; } my $found = $pkg->find( %common_opts_with_ci, filter => \%filter, allow_empty => 1 ); if ($found) { # Infinitevol of the same name exists. $self = $pkg->_element_exists_handler( create_opts => \%opts, purge_opts => \%purge_opts, nacltask_if_exists => $if_exists, exception => $exception ); if ($self) { $self->_is_infinivol($is_infinivol); } else { no warnings qw(exiting); redo CREATE; } } else { # Infinitevol exists, but is of a different name $Log->exit(); $exception->throw(); } }; my $nacltask_randomize_attr = delete $opts{nacltask_randomize_attributes}; my $hash = delete $opts{nacltask_attributes}; if ( $nacltask_randomize_attr ) { my $attr = get_random_hash(hashref => $hash); $Log->debug(sub { 'Random attributes: '.Dumper($attr) }); %opts = ( %{ $attr }, %opts ); } try { # Deleting aggregate,aggr-list,aggr-list-multiplier if auto-select-aggregates is provided to create a Flex Groups if(defined ($opts{'auto-select-aggregates'})){ delete $opts{'aggregate'}; delete $opts{'aggr-list'}; delete $opts{'aggr-list-multiplier'}; } $self = $pkg->SUPER::create(%opts); $$was_created = 1; $self->job_component(${$opts{job_component}}); # Wait for creation if any of the following is true: # 1. nacltask_wait = 1 # 2. We need to mount in 7Mode # 3. Need to perform the efficiency operations if ($wait || $need_to_mount_7m || $nacltask_storage_efficiency) { $self->wait_for_creation(%wait_for_creation_opts); } if ($nacltask_verify) { $self->taskverify_create(); } ## end if ($nacltask_verify) if ($need_to_mount_7m) { $self->mount(%common_opts); } # Enable storage efficiency option for volume if # nacltask_storage_efficiency is passed if ($nacltask_storage_efficiency) { my @actions = ("on", "start"); foreach my $action (@actions) { my $method = "efficiency_" . $action; $self->$method(%common_opts); } ## end foreach my $action (@action) } ## end if ( defined $nacltask_storage_efficiency) # Keep this last: if the state needs to be modified to offline # (for example) then the other operations we need to do # might not work if ($need_to_modify_state_7m) { $self->modify(%common_opts, state => $opts{state}); } } ## end try catch NACL::C::Exceptions::Volume::AlreadyExists with { my $exception = shift; if ($random_name_generated) { $Log->trace('Random name collision, regenerate a ' . 'new name and retry'); $opts{volume} = $pkg->random_name_generator(%$rng_args); no warnings qw(exiting); redo CREATE; } else { $Log->trace('The volume already exists'); $self = $pkg->_element_exists_handler( create_opts => \%opts, purge_opts => \%purge_opts, nacltask_if_exists => $if_exists, exception => $exception ); if ($self) { $self->_update_infinivol_val_in_self(%opts); } else { no warnings qw(exiting); redo CREATE; } } ## end else [ if ($random_name_generated)] } catch NACL::C::Exceptions::Volume::AggregateRequiredForCreate with { # Guaranteed to be an attempt to re-create an infinivol if we # reached here. (if it is not an infinivol, an "aggregate" value # would have been auto-determined) $handle_exception->( exception => $_[0], is_infinivol => 1 ); } catch NACL::C::Exceptions::Volume::InvalidDataConstituentName with { $handle_exception->( exception => $_[0], is_infinivol => 0 ); } catch NACL::C::Exceptions::Volume::VserverNotInAggrList with { NACL::STask::Vserver->add_to_aggr_list( %common_opts_with_ci, 'aggr-list' => [$opts{aggregate}], vserver => $opts{vserver} ); 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); $self->_check_in_metrocluster_node( command_interface => $opts{command_interface}, method_used => "create", %mcc_opts, ); my $param_global = param_global(); my $snaplock_type = $param_global->get('NACL_SNAPLOCK_TYPE'); if (defined $snaplock_type && (!defined ($opts{type}) || $opts{type} !~ /DP/i)) { my $global_snaplock_default_period = $param_global->get('NACL_SNAPLOCK_DEFAULT_PERIOD'); my $snaplock_default_period = $global_snaplock_default_period; my $global_snaplock_maximum_period = $param_global->get('NACL_SNAPLOCK_MAXIMUM_PERIOD'); my $global_snaplock_minimum_period = $param_global->get('NACL_SNAPLOCK_MINIMUM_PERIOD'); my $global_snaplock_num_files = $param_global->get('NACL_SNAPLOCK_NUM_FILES'); my $global_snaplock_autocommit_period = $param_global->get('NACL_SNAPLOCK_AUTOCOMMIT_PERIOD'); # A value of "default" means use the default as set by the library, i.e. 5 seconds. $global_snaplock_autocommit_period = '5seconds' if (defined $global_snaplock_autocommit_period && $global_snaplock_autocommit_period eq 'default'); my $snaplock_autocommit_period = $global_snaplock_autocommit_period; # A value of '5s' is set when snaplock-num-files is set and snaplock-autocommit-period is not set. $snaplock_autocommit_period = '5seconds' if( ($global_snaplock_num_files) && (!$global_snaplock_autocommit_period) ); my %primary_keys = (volume => $self->volume(), vserver => $self->vserver()); # Log snaplock-related information NACL::CS::VolumeSnaplock->fetch( command_interface => $command_interface, filter => \%primary_keys, %common_opts, ); NACL::CS::SnaplockComplianceClock->fetch(command_interface => $command_interface, %common_opts); # Set snaplock defaults $snaplock_default_period ||= '60seconds'; my %modify_args; # Commented out because autocommit will be available only in chunk 2 # $modify_args{'snaplock-autocommit-period'} = $snaplock_autocommit_period # if (defined $snaplock_autocommit_period); $modify_args{'default-retention-period'} = $snaplock_default_period if (defined $snaplock_default_period); $modify_args{'maximum-retention-period'} = $global_snaplock_maximum_period if (defined $global_snaplock_maximum_period); $modify_args{'minimum-retention-period'} = $global_snaplock_minimum_period if (defined $global_snaplock_minimum_period); if (keys %modify_args) { # If snaplock_num_files is set by the user, then creating that many number of files my @files; my $vol_node = $self->get_local_node_name(%common_opts); if ($global_snaplock_num_files) { @files = $self->file_create( %common_opts, filecount => $global_snaplock_num_files, 'nodescope-node-name' => $vol_node, nacltask_wait => 0 ); map {$_->write(%common_opts, content => 'Dummy line' )} @files; } NACL::C::VolumeSnaplock->modify(%primary_keys, %modify_args, %common_opts, command_interface => $command_interface); # The autocommit-period can be of the following forms: # Just an integer (5, 10), which would mean it's in term of seconds # Or with a suffix, like "5s", "1m" and so on. # Convert these values into an integer so that Tharn::snooze() works. if (@files) { my %factors = ('s' => 1, 'm' => 60, 'h' => 3600); $snaplock_autocommit_period =~ /(\d+)(\w)?/; my $num = $1; my $suffix = $2 || 's'; my $seconds = $num * $factors{$suffix}; # Wait for files to get auto-committed to WORM Tharn::snooze($seconds); foreach my $file (@files) { $Log->comment('===== BEGIN SNAPLOCK FINGERPRINT (path=' . $file->path() . ') ====='); $file->show_fingerprint(%common_opts); $Log->comment('===== END SNAPLOCK FINGERPRINT (path=' . $file->path() . ') ====='); } # If autocommit was not set by the user, disable it after the # files have been committed # Commented out because autocommit will be available only in chunk 2 #if (!$global_snaplock_autocommit_period) { #NACL::C::VolumeSnaplock->modify(%primary_keys, %common_opts, #'snaplock-autocommit-period' => 'none'); #} } } } $Log->exit(); return $self; } ## end sub create =head2 purge $volume->purge(%other_options); NACL::STask::Volume->purge( command_interface => $command_interface, volume => $volume_name, vserver => $vserver_name, %other_options ); (Class or instance method) This method deletes an volume, as well as detaching or deleting other features that may be preventing this volume from being deleted. =over =item Partially deleting a volume (applicable from FS.0 onwards) In FS.0, the volume undelete feature was introduced (see https://wikid.netapp.com/w/WAFL/Volume_Undelete/TDS). With this feature, it is possible to partially delete a volume. By default, the C method permanently deletes the volume. To partially delete a volume, provide C<< force => 'false' >> in the call to purge. =back =over =item Options =over =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<< volume=>$vol_name >> (Required for class method, Not Applicable for instance method) Name of volume. =item C<< vserver=>$vserver_name >> (Required for C-mode for class method, ignored for 7-mode, Not Applicable for instance method) Name of vserver containing volume =item C<< force => 'true' | 'false' >> (Optional; defaults to 'true'; applicable only from FS.0 onwards; not applicable for constituent volumes) Set this to 'false' to partially delete a volume. =item C<< nacltask_verify => $nacltask_verify_boolean >> (Optional) If '0' (default), verification will not be performed. If '1', verification will be performed to ensure that the deletion did happen successfully. =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 volume did not exist (value will be 0; this scenario is possible when nacltask_if_purged => "pass") or whether the volume was deleted (value will be 1). =item C<< if_purged => die | pass >> B. This option is deprecated. Use C instead. =item C<< nacltask_if_purged => die | pass >> (Optional, defaults to "die") Specifies what to do if the volume we're trying to purge no longer exists. A value of C (the default) will result in an exception being thrown. A value of C will mean that the exception that would normally occur when we're trying to delete a volume that does not exist is suppressed. =item C<< nacltask_need_to_unmount => 0 | 1 >> (Optional, defaults to 1) This option (considered only for CMode) specifies to the task whether unmount needs to be run or not. Cases where we don't need to unmount the volume first are for volumes which are vserver root volumes, or if the volume is of type "DC". The current implementation ignores the errors that occur if unmount fails as a result of either of the above cases. The optimization this option provides is that, if set to 1, the unmount command won't even be invoked. It is unlikely that script writers will ever need to pass this. This is more likely necessary for other purge routines which need to purge volumes (such as Aggregate->purge, Vserver->purge) which would have knowledge of the type of the volume being purged. =item C<< nacltask_destroy_corrupt_volume => 0 | 1 >> (Optional, defaults to 0) If 7-Mode CLI command is used to delete the inconsistent volume,filer throws the prompt 'This volume is marked corrupt and could be useful for debugging purposes, are you sure you want to proceed? ' and asks for user confirmation. If this option is set to 1, inconsistent volume will be deleted. This option is not applicable and ignored for 7-Mode ZAPI and C-Mode CLI/ZAPI. =item C The options accepted for MCC configuration replication verification is documented at L. =item C<< nacltask_destroy_volume_clones => 0 | 1 >> (Optional, defaults to 0) This parameter specifies what action needs to be taken care, if the volume has a clones associated with it. If this option is set to 1, volume clones will also being purged. =back =back =over =item Exceptions =over =item C This type of exception is thrown when an attempt is made to delete volume that does not exists. =item C This type of exception is thrown when verification fails for the deleted volume. =back =back =cut sub purge { $Log->enter(); my ($pkg_or_obj, @args) = @_; state $additional_spec = { # 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( ) }, nacltask_verify => {type => BOOLEAN, default => 0}, nacltask_need_to_unmount => {type => BOOLEAN, default => 1}, nacltask_destroy_corrupt_volume => {type => BOOLEAN, default => 0}, nacltask_destroy_volume_clones => {type => BOOLEAN, default => 0}, $pkg_or_obj->_mcc_validate_spec(), # force => true required to delete constituent volumes and, # from FS.0, to permanently delete the volume. force => {type => SCALAR, default => 'true'}, _was_deleted => { type => SCALARREF, optional => 1 }, }; # Cannot use _if_action_completed_already_validate_spec directly since it # defaults the value to 'die' so we won't know whether if_purged or # nacltask_if_purged was provided by the caller. my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => $additional_spec, ); my ( %task_opts, %mcc_opts ); $pkg_or_obj->_move_common_mcc_opts( source => \%opts, target => \%mcc_opts, ); $pkg_or_obj->_move_nacltask_options( source => \%opts, target => \%task_opts, ); $task_opts{if_purged} = delete $opts{if_purged}; my $if_purged = $task_opts{nacltask_if_purged} || $task_opts{if_purged} || 'die'; my $verify = $task_opts{nacltask_verify}; my $need_to_unmount = $task_opts{nacltask_need_to_unmount}; my $force = delete $opts{force}; my $was_deleted = delete $opts{_was_deleted}; $$was_deleted = 0; # Used for when nacltask_if_purged => 'pass' and the volume isn't found # In these cases we should return immediately, rather than try the # commands after it. my $need_to_return; # Invoke unmount and then offline the volume # Unmount only necessary for CMode and for non-root volumes try { if ( ($opts{command_interface}->mode() eq 'CMode') && ($need_to_unmount)) { $pkg_or_obj->unmount(%opts); } } catch NACL::C::Exceptions::Volume::DoesNotExist with { my $exception = shift; if ($if_purged =~ /pass/i) { $need_to_return = 1; } else { $Log->exit(); $exception->throw(); } } catch NACL::C::Exceptions::Volume::UnmountRoot with { $Log->trace('Ignoring the error when we try to unmount a ' . "vserver's root volume"); } catch NACL::C::Exceptions::Volume::UnsupportedUnmount with { $Log->trace('Ignoring this error when we try to unmount a ' . "volume of type DP/DC"); } catch NACL::C::Exceptions::Volume::OperationNotPermittedOnDrVolume with { $Log->trace('skipping unmount as this volume belongs to a vserver in DR relationship'); }; if ($need_to_return) { $Log->exit(); return; } my $offline = sub { my ($state) = validate_pos(@_, {type => SCALAR}); $pkg_or_obj->modify( %opts, state => $state, # If this is a trad vol, and being created, let's # assume the modify could take a very long time, # just like for aggregates 'method-timeout' => AGGREGATE_TIMEOUT, # If already offline don't fail nacltask_if_modified => 'pass' ); }; my %primary_keys; $pkg_or_obj->_copy_primary_keys( source => \%opts, target => \%primary_keys ); my %common_opts; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts ); my $eff_off = sub { my %additional_args = @_; NACL::STask::VolumeEfficiency->off(%primary_keys, %common_opts, %additional_args); }; try { if ($opts{command_interface}->is_7mode()) { $offline->('offline'); } else { $offline->('force-offline'); } } catch NACL::C::Exceptions::Volume::DoesNotExist with { my $exception = shift; if ($if_purged =~ /pass/i) { $need_to_return = 1; } else { $Log->exit(); $exception->throw(); } } catch NACL::C::Exceptions::Volume::StateUnknown with { $Log->trace('Modifying state to offline failed because ' . 'volume state is "-"'); } catch NACL::C::Exceptions::Volume::UnsupportedDELOperation with { # deleting the retention volume NACL::C::VolumeRecoveryQueue->purge(%opts); $need_to_return = 1; } catch NACL::C::Exceptions::Volume::ConstituentsInInconsistentState with { my $orig_exception = shift; #some constituents might be in offline state. #bring em online before turning eff off. $pkg_or_obj->modify( %primary_keys,%common_opts, state => 'online', nacltask_wait => 1, nacltask_if_modified => 'pass' ); # This could be because: # 1) efficiency is running OR # 2) The constituents are really in an inconsistent state. # We should throw the exception for case 2. Let's try switching # off efficiency. If already off, then re-throw the exception. try { $eff_off->(ignore_disabled => 0); } otherwise { $Log->exit(); $orig_exception->throw(); }; # If we've reached here, then efficiency was successfully switched # off, so let's redo offline. $offline->('force-offline'); }; if ($need_to_return) { $Log->exit(); return; } my $job; my %additional_delete_opts = (); my $purge_clone = $task_opts{nacltask_destroy_volume_clones}; $additional_delete_opts{'destroy_corrupt_volume'} = $task_opts{nacltask_destroy_corrupt_volume}; REDO: try { $pkg_or_obj->delete( force => $force, %opts, job_component => \$job, %additional_delete_opts ); $$was_deleted = 1; } catch NACL::C::Exceptions::Volume::RebalancerRunning with { $Log->trace('Stop the capacity rebalance process'); NACL::C::CapacityBalanceVolumeRebalance->stop( %common_opts, vserver => $opts{vserver}, volume => $opts{volume}, 'storage-service' => '*', ); goto REDO; } catch NACL::C::Exceptions::Volume::ApplicationVolumeRemove with { my $exception = shift; $Log->comment("Volume is part of application, removing the volume from application"); NACL::C::ApplicationVolume->remove( %common_opts, vserver => $opts{vserver}, volume => $opts{volume}, application => "*"); goto REDO; } catch NACL::C::Exceptions::Volume::HasClones with { my $exception = shift; $Log->trace('Volume has clones'); if ($purge_clone == 0) { $Log->exit(); $exception->throw(); } my @clones = NACL::STask::VolumeClone->find( %common_opts, filter => { vserver => $opts{vserver}, 'parent-volume' => $opts{volume}, } ); foreach my $flex_clone (@clones) { $flex_clone->cast_to_volume()->purge( %common_opts, nacltask_destroy_volume_clones => 1 ); } goto REDO; }; if ($job) { $Log->trace("Wait on the job ID"); $pkg_or_obj->wait_on_job( 'method-timeout' => AGGREGATE_TIMEOUT, job_component => $job, %opts ); } $pkg_or_obj->_check_in_metrocluster_node( command_interface => $opts{'command_interface'}, method_used => "purge", %mcc_opts, %primary_keys, ); if ($verify) { $pkg_or_obj->taskverify_purge(%opts); } $Log->exit(); } =head2 wait_for_creation # Instance method: wait for volume to get created and come online $volume->wait_for_creation(); # Class method: wait for volume to get created and come online NACL::STask::Volume->wait_for_creation( "method-timeout" => $timeout, command_interface => $command_interface, volume => $volume, vserver => $vserver, job_component => $job_component, ); Wait for the volume creation to complete. If a job for creating this volume is known (because the mode is C-mode and NACL::STask::Volume->create was used to create this object, or because it was passed in using the "job_component" option) then this waits for that job. Otherwise, it waits until the "state" of the volume is "online". If the state of the volume becomes something unexpected (currently something other than "creating", "mixed" or "mounting") then a L exception is thrown. Note that at creation time, it is possible to specify the volume should be created in some state other than "online". In that case, we can specify to this method, what is the terminal state which would denote the volume has been created. For example: # Specify at creation time to offline the volume my $volume = NACL::STask::Volume->create(..., state => 'offline', nacltask_wait => 0); ... some code here ... # Waiting for volume to get created and reach the "offline" state $volume->wait_for_creation(state => 'offline'); =over =item Options =over =item C<< job=>$job_component >> (Optional for class method, Not Applicable for instance method) A NACL::C::Job object representing the C-mode Job used to create this volume. It is recommended to pass job_component in case of async libraries e.g. volume-create-async. =item C<< command_interface=>$command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume=>$volume_name >> (Required for class method, Not Applicable for instance method) Name of volume. =item C<< vserver=>$vserver_name >> (Required for C-mode for class method, ignored for 7-mode, Not Applicable for instance method) Name of vserver containing volume. =item C<< state => $state >> (Applicable only for CMode) Specify the terminal state which would mean that the volume has been created. Passing a value for this would be necessary if we specified a value for state at creation time. =item C<< "method-timeout"=>$timeout >> (Optional) How long in seconds to wait for the job to finish or for the state to become "online". Defaults to 7200 (2 hours). =item C<< polling_interval => $interval >> (Optional) The interval, in seconds, at which to either show the progress of the job (if we're watching its progress) or the interval at which to poll the value of the "state" field. This defaults to 10 seconds if not provided. =item C<< apiset_must=>$ruleset >> (Optional)See L =item C<< apiset_should=>$ruleset >> (Optional)See L =back =back =cut sub wait_for_creation { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { state => { type => SCALAR, default => 'online' }, $pkg_or_obj->_job_validate_spec(), polling_interval => { type => SCALAR, default => POLL_DELTA }, }, ); my $state = delete $opts{state}; my $till_value; # Map a state value of "force-online" and "force-offline" # to a terminal state of "online" and "offline" respectively if ($state eq 'force-online') { $till_value = 'online'; } elsif ($state eq 'force-offline') { $till_value = 'offline'; } else { $till_value = $state; } %opts = $pkg_or_obj->_extract_job(%opts); $opts{'method-timeout'} ||= AGGREGATE_TIMEOUT; $pkg_or_obj->wait_for_completion( %opts, attribute_to_check => 'state', till_value => $till_value, valid_intermediate_states => $pkg_or_obj->_valid_intermediate_states(), ); $Log->exit(); } ## end sub wait_for_creation =head2 add_members Same as L with the addition of support for waiting for the operation (or job) to complete (through the new C option). =over =item Options =over =item C<< Other Options >> All of the options accepted by the Volume component's add_members. (See L) =item C<< "nacltask_wait" => 0|1 >> (Optional, defaults to being 1) This new option (not supported by the component call) can be used to specify whether the task method should wait for the operation (or job) to complete or not. =item C<< "polling_interval" => $interval >> (Optional) If nacltask_wait is set to 1, this is the interval at which to show the progress of the job. It defaults to 10 seconds. =back =back =cut sub add_members { $Log->enter(); my $pkg_or_obj = shift; my %orig_opts = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => {nacltask_wait => {type => BOOLEAN, default => 1}}, allow_extra => 1 ); # could not find add members in the man pages # so not sure if we can do cleanup for it my %wait_for_add_members_opts; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%wait_for_add_members_opts ); # Move nacltask_* options out of %orig_opts so that it contains # only component options $pkg_or_obj->_hash_move( source => \%orig_opts, target => \%wait_for_add_members_opts, move => [qw(polling_interval)] ); $pkg_or_obj->_move_nacltask_options( source => \%orig_opts, target => {} ); my $wait = $opts{nacltask_wait}; my $job; $orig_opts{job_component} ||= \$job; # Invoke add_members() $pkg_or_obj->SUPER::add_members(%orig_opts); if ($job) { # If there was a job ID, then store it in the object $pkg_or_obj->job_component($job); $wait_for_add_members_opts{job_component} = $job; } ## end if ($job) if ($wait) { my $pk_opts = $pkg_or_obj->get_primary_keys_options(%opts); $pkg_or_obj->wait_for_add_members(%wait_for_add_members_opts, %{$pk_opts}); } $Log->exit(); } ## end sub add_members =head2 wait_for_add_members $volume->wait_for_add_members( "method-timeout" => $timeout, ); NACL::STask::Volume->wait_for_add_members( "method-timeout" => $timeout, command_interface => $command_interface, volume => $volume, vserver => $vserver, job_component => $job_component, ); (Class or Instance method) Wait for the volume add-members to complete. This method waits on the job ID obtained during the invocation of 'add-members' on a striped volume. =over =item Options =over =item C<< job_component=>$job_component >> (Required for class method, Not Applicable for instance method) A NACL::C::Job object representing the C-mode Job used to add member volumes. =item C<< command_interface=>$command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume=>$volume_name >> (Required for class method, Not Applicable for instance method) Name of volume. =item C<< vserver=>$vserver_name >> (Required for C-mode for class method, ignored for 7-mode, Not Applicable for instance method) Name of vserver containing volume. =item C<< "method-timeout"=>$timeout >> (Optional) How long in seconds to wait for the job to finish or for the state to become "online". Defaults to 7200 (2 hours). =item C<< polling_interval => $interval >> (Optional) The interval, in seconds, at which to either show the progress of the job. This defaults to 10 seconds. =item C<< apiset_must=>$ruleset >> (Optional)See L =item C<< apiset_should=>$ruleset >> (Optional)See L =back =back =cut sub wait_for_add_members { $Log->enter(); my $pkg_or_obj = shift; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { job_component => {isa => 'NACL::C::Job', optional => 1}, polling_interval => {type => SCALAR, default => POLL_DELTA}, }, ); $opts{job_component} = delete $opts{job_component} || $pkg_or_obj->job_component(); $opts{'method-timeout'} ||= AGGREGATE_TIMEOUT; $pkg_or_obj->wait_on_job(%opts); $Log->exit(); } ## end sub wait_for_add_members =head2 modify NACL::STask::Volume->modify( command_interface => $ci, volume => $vol_name, vserver => $vserver_name, nacltask_if_modified => $if_modified, # Default is "die" nacltask_if_mounted => $action, # Defualt is "unmount" nacltask_verify => $boolean, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs %other_opts ); or $vol_stask_obj->modify(%other_opts, nacltask_if_modified => $if_modified); (Class or instance method) Modifies properties of the volume. This method provides additional services beyond what's inherent to the product's volume modification commands, as controlled and described by the new C and C options. The timeout is defaulted to 300 seconds. If a volume needs to be offline which is mounted then a new argument is accepted through which we can specify what to do if the volume is already mounted. The default is "unmount" which will unmount the volume and then perform the modify operation. This way, regardless of whether the volume was mounted or not, it will end up being offline or restricted. =over =item Options =over =item C<< nacltask_if_modified => die|pass >> (Optional, defaults to "die") For newer SN builds, attempting to offline an already offline volume results in an error. Similarly, attempting to force-offline an already offline volume results in an error. This option controls whether this should result in an exception, or whether the exception should be suppressed. =over =item C (The default) If this is the value of nacltask_if_modified, then if the product raises an error because the volume is already in the state we want to modify to then this exception is propagated. =item C If this is the value of nacltask_if_modified, then if the product raises an error because the volume is already in the state we want to modify to then this exception is suppressed.. =back =item C<< nacltask_verify => 0|1 >> (Optional, defaults to 0) Specifies whether to verify that all the fields got modified to the values specified in the call. If one or more of the fields don't match, then a L exception is thrown. =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =item C<< nacltask_if_mounted => die|unmount >> (Optional, defaults to "die") Attempting to offline a mounted volume results in an error. Similarly, attempting to restrict a mounted volume results in an error. This option controls whether this should result in an exception, or whether the exception should be suppressed. The default is C, which means that it will unmount and then perform offline or restrict the volume. If the value is specified as C, then a L exception is thrown. =item C<< nacltask_wait => 0 | 1 >> (Optional, defaults to 1, applicable only if an async ZAPI was used) If we're restricting the infinite volume using ZAPI, then an "async" ZAPI is used. The "async" ZAPI returns immediately and starts a background job. If set to 1 (the default), the method will wait on the job. Note that this is applicable only for infinite volumes through ZAPI: in all other cases the operation does not start a job. =item Other options All of the other options accepted by L. =back =back =over =item Exceptions =over =item C This type of exception is thrown when verification for volume modify has failed. =item C< NACL::C::Exceptions::Volume::AlreadyModified> This type of exception is thrown when an attempt is made to modify a volume which is already modified. item C This type of exception is thrown when an attempt is made to offline a volume which is mounted. =back =back =cut sub modify { $Log->enter(); my ($pkg_or_obj, @args) = @_; if ($Log->may_debug()) { my $me = (caller(0))[3]; $Log->debug("Options to '$me' are:\n" . Dumper({@args})); } my $callbacks = { "Value of 'nacltask_if_mounted' should be either " . "'unmount' or 'die'" => sub { $_[0] =~ /^(die)|(unmount)$/ } }; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { nacltask_if_modified => $pkg_or_obj->_if_action_completed_already_validate_spec( name => 'nacltask_if_modified' ), nacltask_verify => { type => SCALAR, default => 0 }, $pkg_or_obj->_cleanup_validate_spec(), nacltask_retry_tries_count => { type => SCALAR, default => 10 }, nacltask_retry_sleep_time => { type => SCALAR, default => 5 }, nacltask_retry_num_times_code_ran => { type => SCALARREF, optional => 1 }, nacltask_if_mounted => { type => SCALAR, default => 'unmount', callbacks => $callbacks }, nacltask_wait => { type => SCALAR, default => 1 }, 'method-timeout' => { type => SCALAR, default => 300 }, }, allow_extra => 1 ); my %nacltask_options; $pkg_or_obj->_move_nacltask_options( source => \%opts, target => \%nacltask_options ); my $opts_for_cleanup; if ($nacltask_options{'nacltask_to_cleanup'} || $nacltask_options{'nacltask_cleanup_manager'}) { my %args_to_common_modify_for_cleanup = ( 'nacltask_to_cleanup' => $nacltask_options{'nacltask_to_cleanup'} ); $args_to_common_modify_for_cleanup{'nacltask_cleanup_manager'} = $nacltask_options{'nacltask_cleanup_manager'} if ( $nacltask_options{'nacltask_cleanup_manager'}); $opts_for_cleanup = $pkg_or_obj->_common_modify_for_cleanup( %args_to_common_modify_for_cleanup, %opts, ); } my $if_modified = $nacltask_options{nacltask_if_modified}; my $if_mounted = $nacltask_options{nacltask_if_mounted}; my %retry_opts; $pkg_or_obj->_copy_retry_params( source => \%nacltask_options, target => \%retry_opts ); my $dummy; $opts{job_component} ||= \$dummy; my $count = 0; my $tries_count = $retry_opts{tries_count}; my $sleep_time = $retry_opts{sleep_time}; my $eff_stop = sub { my %additional_args = @_; my %primary_keys; $pkg_or_obj->_copy_primary_keys( source => \%opts, target => \%primary_keys ); my %common_opts; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts ); $additional_args{ignore_disabled}=1; NACL::STask::VolumeEfficiency->stop(%primary_keys, %common_opts, %additional_args); }; my $throw_after_retry = sub { my $exception = $_[0]; $exception->set_text("After $tries_count iterations " . "with a sleep of $sleep_time seconds between iterations, " . "the method still failed. Original failure:\n" . $exception->text()); $Log->exit(); $exception->throw(); }; my $redo_sub = sub { no warnings 'exiting'; redo AGAIN; }; AGAIN: { use warnings 'exiting'; try { $pkg_or_obj->SUPER::modify(%opts); } catch NACL::C::Exceptions::Volume::CurrentlyBusy with { my $exception = $_[0]; $count++; # Might be busy because efficiency is running. Switch it off # only the first time we see this error. if ($count == 1) { try { $eff_stop->(); } otherwise { #ignore exceptions from Efficiency stop. }; } elsif ($count < $tries_count) { Tharn::snooze($sleep_time); } else { $throw_after_retry->($exception); } $redo_sub->(); } catch NACL::C::Exceptions::Volume::AlreadyModified with { my $exception = $_[0]; if ($if_modified =~ /pass/i) { $Log->debug("Exception suppressed because 'if_modified' " . "was sent as '$if_modified'"); } else { $Log->exit(); $exception->throw(); } } catch NACL::C::Exceptions::Volume::MustBeUnmounted with { my $exception = $_[0]; if ($if_mounted =~ /unmount/i) { my %unmount_opts; $pkg_or_obj->_copy_primary_keys( source => \%opts, target => \%unmount_opts ); $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%unmount_opts ); $pkg_or_obj->unmount(%unmount_opts); no warnings 'exiting'; $redo_sub->(); } else { $Log->exit(); $exception->throw(); } } catch NACL::C::Exceptions::Volume::VserverLocked with { my $exception = $_[0]; $count++; if ($count < $tries_count) { Tharn::snooze($sleep_time); } else { $throw_after_retry->($exception); } $redo_sub->(); }; } my $verify = $nacltask_options{nacltask_verify}; # If we need to verify, then we have to wait for the operation to # first complete my $wait = $nacltask_options{nacltask_wait} || $verify; if ($wait) { # if ( exists $opts{encrypt} ) { # # wait for vge inplace rekey scan to complete # $Log->comment("Waiting for volume conversion scan to complete"); # NACL::STask::VolumeEncryption->wait_for_completion( # command_interface => $opts{command_interface}, # volume => $opts{volume}, # vserver => $opts{vserver}, # encrypt => $opts{encrypt}, # ); # $Log->comment("Volume conversion complete"); # } $pkg_or_obj->_wait_for_async_zapi(%opts); } if ($verify) { my %verify_opts = %opts; delete $verify_opts{job_component}; $pkg_or_obj->taskverify_modify(%verify_opts); } $pkg_or_obj->_register_for_cleanup(%{$opts_for_cleanup}) if (ref $opts_for_cleanup); $Log->exit(); } ## end sub modify # Definition is from _AggregateVolume. sub _default_rng_suffix { return 'vol'; } =head2 fullname $volume->fullname(); (Instance method) This method constructs the full path from the volume object and returns it. Path returned will be in the format below : cluster_name://vserver_name/volume_name. If the command_interface present in the object is of type NACL::C::Vserver, then it is not be possible to get the full path since cluster name will not be present. Exception will be thrown in this case. Applicable only for CMode. 7Mode is not supported as of now. =over =item Exceptions =over =item C This type of exception is thrown when there is a failure to construct full volume path because of missing cluster name or volume name or vserver name. =back =back =cut sub fullname { $Log->enter(); my $vol_obj = shift; my $volpath = undef; my $vserver_name = $vol_obj->vserver(); my $volume_name = $vol_obj->volume(); my $cluster_name; my $vol_ci = $vol_obj->command_interface(); if ($vol_ci->isa('NACL::C::Cluster') || $vol_ci->isa('NACL::C::Node')) { my $cluster_cs_obj = NACL::CS::ClusterIdentity->fetch( command_interface => $vol_ci, requested_fields => ['name'], ); $cluster_name = $cluster_cs_obj->name(); } elsif ($vol_ci->isa('NACL::C::Vserver')) { $Log->exit(); NATE::Exceptions::Argument->throw( "Missing cluster name. Path cannot be constructed " . "if the command_interface present in the volume " . "object is of type NACL::C::Vserver"); } ## end elsif ( $vol_ci->isa('NACL::C::Vserver'...)) if ($cluster_name && $vserver_name && $volume_name) { $volpath = "$cluster_name://$vserver_name/$volume_name"; } else { $Log->exit(); NATE::Exceptions::Argument->throw( "Missing values for the construction of a full volpath: " . "cluster_name = \"$cluster_name\", vserver_name = " . "\"$vserver_name\", volume_name = \"$volume_name\"."); } ## end else [ if ( $cluster_name && ...)] $Log->exit(); return $volpath; } ## end sub fullname =head2 fill_volume_with_file_reserve $volume->fill_volume_with_file_reserve( filename => $filename, percent_to_fill => $percent_to_fill, ); (Instance method) Fill volume space to user specified percent with file reservation enabled. No need to fill file holes. Based on the assumption that each volume will have 5% snapshot reserve space plus 5% spill over limit, so the given number of percent_to_fill is an integer number limited between 1 to 89. If not given by user, it defaults to 84 because 85% is a default percent to trigger volume autogrow. More details can be found in L<< NACL::STask::Mkfile->mkfile | lib-NACL-STask-Mkfile-pm/mkfile >> =over =item Options =item C<< filepath => $string >> (Required) : File name with full volume path to be created. =item C<< "percent_to_fill" => $integer >> (Optional) : Desired percent to fill volume space. Default is 84. =item C<< vserver => $vserver >> (Optional) : This is required for Cmode only and only if user wants to create a file by enabling file reservation. =back =over =item Exceptions =over =item C This type of exception is thrown when given percentage to fill is not in the range of 1-89. =back =back =cut sub fill_volume_with_file_reserve { $Log->enter(); my $vol_obj = shift; my %opts = validate( @_, { filename => {type => SCALAR, optional => 0}, percent_to_fill => {type => SCALAR, optional => 1, default => 84}, vserver => {type => SCALAR, optional => 1}, } ); my $filename = $opts{filename}; my $percent_to_fill = $opts{percent_to_fill}; my $vserver = $opts{vserver}; # Check percent_to_fill is within 1 to 89 range if ($percent_to_fill < 1 || $percent_to_fill > 89) { $Log->exit(); NATE::BaseException->throw("Error: Given percent to fill number must " . "be between 1 and 89. You gave $percent_to_fill."); } ## end if ( $percent_to_fill ...) my ($volume_state, $vol_total_size, $vol_used_size, $vol_percent_used, $size_to_fill, $percent_can_be_filled, ); # Get current vol state $volume_state = $vol_obj->state(requested_fields => [qw(total used)]); # Get vol total size $vol_total_size = $volume_state->total(); # Get current vol space used percent $vol_used_size = $volume_state->used(); $vol_percent_used = ceil($vol_used_size / $vol_total_size * 100); # Check user specified percent_to_fill with current volume used capacity $percent_can_be_filled = $percent_to_fill - $vol_percent_used; # Check user specified percent_to_fill is greater than current # volume used space if ($percent_can_be_filled <= 0) { $Log->comment("Volume is already $vol_percent_used% filled. " . "No need to fill more."); $Log->exit(); return; } ## end if ( $percent_can_be_filled...) # Calculate real size to be used by mkfile $size_to_fill = int($vol_total_size * $percent_can_be_filled / 100); # Get common component params: apiset_must, apiset_should, etc my %apiset_must_should = (); $vol_obj->_copy_common_component_params( source => \%opts, target => \%apiset_must_should ); $apiset_must_should{'vserver'} = $vserver if (defined $vserver); NACL::STask::VolumeFile->create( command_interface => $vol_obj->command_interface(), vserver => $vserver, path => $filename, size => $size_to_fill, %apiset_must_should ); $Log->exit(); } # end sub fill_volume_with_file_reserve =head2 compare_space_saving $src_vol_obj->compare_space_saving ( destination_volume => $dst_vol_specification, error_tolerance => $percent_error, ); (or) NACL::STask::Volume->compare_space_saving ( source_volume => $src_vol_specification, destination_volume => $dst_vol_specification, type => 'dedupe', ); (Class or Instance method) This method compares the space saved between a source volume and a destination volume. Space saving can be due to dedupe, compression or both. This method allows user to pass the type of space saving to be used for comparison. If the two values are the same or the difference is within the error tolerance percentage passed, the method returns successfully. Else throws L exception. Supports CMode CLI/ZAPI and 7Mode CLI/ZAPI. =over =item Options =over =item C<< "command_interface=>$command_interface" >> (Required for class method, Not Applicable for instance method) See L =item C<< "source_volume" => $src_vol_specification >> (Required for class method, Not Applicable for instance method) Source volume object/name to be compared against. Accepts volume efficiency object also. =item C<< "destination_volume=>$dst_vol_specification" >> (Required) Destination volume object/name which has to be compared with source volume. Accepts volume efficiency object also. =item C<< source_vserver=>$vserver_name >> (Optional for C-mode for class method, ignored for 7-mode, Not Applicable for instance method) Name of vserver containing source volume. Required if volume name is passed to source_volume parameter rather than volume object. =item C<< "destination_command_interface=>$command_interface" >> (Optional) See L Command interface of the destination node. Defaults to 'command_interface' of source. Required if destination volume resides on a different cluster (CMode) / different node (7Mode) and volume name is passed to destination_volume parameter rather than vol object. Not required if dst volume is on same cluster but on different node (CMode). =item C<< destination_vserver=>$vserver_name >> (Optional for C-mode, ignored for 7-mode) Name of vserver containing destination volume. Required if volume name is passed to destination_volume parameter rather than volume object. =item C<< type=>$type >> (Optional) Compare space saving obtained due to the type of storage efficiency operation performed. Type can take values of 'dedupe', 'compression' or 'all'. Default is 'all'. =item C<< error_tolerance=>$percentage_value >> (Optional) Default is 0. A number, which specifies percent of error acceptable. This option is to provide tolerance level for comparison. If error_tolerance is 0, then an exception will be thrown if space saving on source and destination does not match exactly. Say, if error_tolerance = '10', difference between source and destination space saving can be upto 10%. =back =back =over =item Exceptions =over =item C This type of exception is thrown when either source/destination vserver name is missing or source volume name is missing. =item C This type of exception is thrown when percentage of saving difference is not within the tolerance limit. =back =back =cut sub compare_space_saving { $Log->enter(); my ($pkg_or_obj) = shift; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { # stask specific options 'type' => { type => SCALAR, default => 'all', callbacks => { "type must be 'dedupe' or 'compression' or 'all'" => sub { $_[0] =~ /^(dedupe)|(compression)|(all)$/i; } } }, source_volume => { type => OBJECT | SCALAR, optional => 1, callback => { "should be either Volume or VolumeEfficiency object or scalar (volume name)" => sub { if (ref $_[0]) { return ( $_[0]->isa('NACL::C::Volume') || $_[0]->isa('NACL::C::VolumeEfficiency') ); } } } }, source_vserver => {type => SCALAR, optional => 1}, error_tolerance => {type => SCALAR, default => 0}, destination_volume => { type => OBJECT | SCALAR, optional => 0, callback => { "should be either Volume or VolumeEfficiency object or scalar (volume name)" => sub { if (ref $_[0]) { return ( $_[0]->isa('NACL::C::Volume') || $_[0]->isa('NACL::C::VolumeEfficiency') ); } } } }, destination_command_interface => {type => SCALARREF, optional => 1}, destination_vserver => {type => SCALAR, optional => 1}, _optional_scalars(qw(volume vserver)) }, allow_extra => 1 ); my $destination_volume = $opts{destination_volume}; my $type = $opts{type}; my $error_tolerance = $opts{error_tolerance}; my ($src_saved, $dst_saved); my $source_volume; my $construct_vol_obj = sub { my %opts = @_; my $src_or_dest = $opts{src_or_dst}; my $vol_obj = $opts{$src_or_dest . '_volume'}; if ( $opts{$src_or_dest . '_volume'} && $opts{$src_or_dest . '_volume'} ->isa('NACL::C::VolumeEfficiency')) { $opts{$src_or_dest . '_volume'} = $vol_obj->get_volume(); if ($src_or_dest =~ /destination/) { $opts{destination_command_interface} = $vol_obj->command_interface(); } $opts{$src_or_dest . '_vserver'} = $vol_obj->vserver() if $vol_obj->command_interface()->mode() eq 'CMode'; } ## end if ( $opts{ $src_or_dest...}) my $ci; if (!(ref($opts{$src_or_dest . '_volume'}))) { if ($src_or_dest =~ /destination/) { $ci = $opts{destination_command_interface} || $opts{command_interface}; } else { $ci = $opts{command_interface}; } if ( !$opts{$src_or_dest . '_vserver'} && $ci->mode() eq 'CMode') { $Log->exit(); NATE::Exceptions::Argument->throw( "Missing $src_or_dest vserver. $src_or_dest vserver is required " . "if $src_or_dest volume represents volume name and not " . "volume object"); } ## end if ( !$opts{ $src_or_dest...}) $vol_obj = NACL::STask::Volume->new( command_interface => $ci, 'volume' => $opts{$src_or_dest . '_volume'}, 'vserver' => $opts{$src_or_dest . '_vserver'}, ); } ## end if ( !( ref( $opts{ $src_or_dest...}))) return $vol_obj; }; $destination_volume = $construct_vol_obj->(%opts, src_or_dst => 'destination'); if (ref $pkg_or_obj) { $opts{source_volume} = $pkg_or_obj; } else { if (!$opts{source_volume}) { $Log->exit(); NATE::Exceptions::Argument->throw( "Missing source_volume. source_volume is required if " . "compare_space_saving is called as a Class method"); } ## end if ( !$opts{source_volume...}) } ## end else [ if ( ref $pkg_or_obj )] $source_volume = $construct_vol_obj->(%opts, src_or_dst => 'source'); if ($type =~ /dedupe/i) { $src_saved = $source_volume->get_one_state_attribute('dedupe-space-saved'); $dst_saved = $destination_volume->get_one_state_attribute( 'dedupe-space-saved'); } elsif ($type =~ /compression/i) { $src_saved = $source_volume->get_one_state_attribute( 'compression-space-saved'); $dst_saved = $destination_volume->get_one_state_attribute( 'compression-space-saved'); } else { $src_saved = $source_volume->get_one_state_attribute('sis-space-saved'); $dst_saved = $destination_volume->get_one_state_attribute('sis-space-saved'); } ## end else [ if ( $type =~ /dedupe/i)] # Convert size to KB from BYTES $src_saved = NACL::C::UnitNormalization::to_kilobytes($src_saved); $dst_saved = NACL::C::UnitNormalization::to_kilobytes($dst_saved); $src_saved = NACL::C::UnitNormalization::strip_unit_spec($src_saved); $dst_saved = NACL::C::UnitNormalization::strip_unit_spec($dst_saved); if ($src_saved ne $dst_saved) { $Log->trace('Space savings on source and destination volumes ' . 'are not the same'); my $saving_diff = ((abs($src_saved - $dst_saved)) / $src_saved) * 100; if ($saving_diff <= $error_tolerance) { $Log->trace('Space savings (Percent difference : ' . $saving_diff . '%) is within the tolerance limit ' . $error_tolerance . '%'); } else { $Log->exit(); NACL::Exceptions::VerifyFailure->throw( 'Percentage of differnce ' . $saving_diff . '% is not within the tolerance limit ' . $error_tolerance . '%. SRC saving : ' . $src_saved . 'KB' . ' DST saving : ' . $dst_saved . 'KB', unexpected_values => { percent_difference => $saving_diff . '%', error_tolerance => $error_tolerance . '%' } ); } ## end else [ if ( $saving_diff <= $error_tolerance)] } else { $Log->trace('Source & Destination savings are a match.'); } $Log->exit(); } ## end sub compare_space_saving =head2 check_snapshot NACL::STask::Volume->check_snapshot ( volume => [ $vol1_spec, $vol2_spec ], snapshot_exclude => $snapshot, ); ( or ) NACL::STask::Volume->check_snapshot ( snapshot_valid => $snapshot_list, volume => [ $vol_specification ], snapshot_invalid => $snapshot_invalid_list, ); (Class method) This method can be used to 1. Compare volumes to verify if they have same set of snapshots (when two volumes are passed without snapshot_valid or snapshot_invalid params). If snapshot_exclude is passed, this gives a list of snapshots that can be missing during comparison. 2. Verify if the volume(s) has(have) got specified set of snapshots ( when one or two volumes with snapshot_valid is passed) 3. verify if a snapshot does not exist on the volume(s) ( when one or two volumes with snapshot_invalid is passed) Returns 1 on success. If verification fails and nacltask_need_exception is '1', NACL::Exceptions::VerifyFailure exception is thrown. If verification fails and nacltask_need_exception is '0', returns 0. Supports CMode CLI/ZAPI and 7Mode CLI/ZAPI. =over =item Options =over =item C<< "command_interface => $command_interface" >> (Required) See L =item C<< "volume" => [$vol_specification] >> (Required) An array reference containing the list of volume objects/names. It can be either one or two volumes. When two volumes are passed, both volumes are compared to check if they contain the same set of snapshots. If it is one volume, just check if the snapshots passed in snapshot_valid/snapshot_invalid are present/not present in the volume respectively. =item C<< "snapshot_valid" => $snapshot_specification >> (Optional) This parameter should be specified as an array-reference. List of snapshots (can be snapshot name or NACL::STask::VolumeSnapshot or NACL::CS::VolumeSnapshot object) whose existense to be verified on the given volume. Can be used with one or two volumes. =item C<< "snapshot_invalid" => $snapshot_specification >> (Optional) List of snapshots (can be snapshot name or NACL::STask::VolumeSnapshot or NACL::CS::VolumeSnapshot object) which should not be present on the given volume. This parameter should be specified as an array-reference. Can be used with one or two volumes. =item C<< "snapshot_exclude" => $snapshot_specification >> (Optional) List of snapshots (can be snapshot name or NACL::STask::VolumeSnapshot or NACL::CS::VolumeSnapshot object) which can be excluded while comparing two volumes for snapshots equivalence. Should be used only when two volumes are compared. This parameter should be specified as an array-reference. =item C<< volume1_vserver => $vserver_name >> (Optional for C-mode when volume object is passed, ignored for 7-mode) Name of vserver containing volume1. Required if volume name is passed to volume parameter rather than volume object. =item C<< "volume2_command_interface => $command_interface" >> (Optional) See L Command interface of the second node. Defaults to 'command_interface'. Required if the second volume resides on a different node (7Mode) or different cluster (CMode) and volume name is passed to volume2 rather than vol object. =item C<< volume2_vserver => $vserver_name >> (Optional for C-mode, ignored for 7-mode) Name of vserver containing second volume. Required if volume name of second volume is passed to volume parameter rather than volume object. =item C<< nacltask_need_exception => $boolean >> (Optional, defaults to 1) If 1 (default), NACL::Exceptions::VerifyFailure exception is thrown when snapshot in snapshot_valid list does not exist on the volume or when two volumes in comparison does not have the same set of snapshots. If 0, exits quietly returning 0 to the user when mismatch occurs. =back =back =over =item Exceptions =over =item C This type of exception is thrown when snapshot list does not match between the given volumes. OR This type of exception is thrown when snapshot does not exist on volumes provided. =item C This type of exception is thrown when vserver name associated with the particular volume is missing or when reference to snapshot object is passed in snapshot list. OR This type of exception is thrown when an attempt is made to pass either of snapshot_valid or snapshot_invalid option and snapshot_exclude option together. =back =back =cut sub check_snapshot { $Log->enter(); my $pkg = shift; $pkg->_verify_invocation(style => 'static_only'); my %opts = $pkg->_common_validate_with( params => \@_, additional_spec => { # stask specific options volume => {type => ARRAYREF, optional => 0,}, volume1_vserver => {type => SCALAR, optional => 1}, volume2_vserver => {type => SCALAR, optional => 1}, volume2_command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface::ONTAP', optional => 1 }, snapshot_valid => {type => ARRAYREF, optional => 1}, snapshot_invalid => {type => ARRAYREF, optional => 1}, snapshot_exclude => {type => ARRAYREF, optional => 1}, nacltask_need_exception => {type => BOOLEAN, default => 1}, _optional_scalars(qw(vserver)) }, ); my $snapshot_valid = $opts{snapshot_valid}; my $snapshot_invalid = $opts{snapshot_invalid}; my $snapshot_exclude = $opts{snapshot_exclude}; my $nacltask_need_exception = $opts{nacltask_need_exception}; my $volume = $opts{volume}; my ($vol1_obj, $vol2_obj); my %vol_snap; my (@snapshot, $flag, $set1, $set2, $second_vol); if (scalar @{$opts{volume}} > 2) { $Log->exit(); NATE::Exceptions::Argument->throw( "More than two volumes not allowed"); } elsif (scalar @{$opts{volume}} == 1 && (!$snapshot_valid && !$snapshot_invalid || $snapshot_exclude)) { $Log->exit(); NATE::Exceptions::Argument->throw( "Pass snapshot_valid or snapshot_invalid" . " that has to be verified on the given volume." . " snapshot_exclude cannot be passed with single volume"); } elsif ($snapshot_exclude && ($snapshot_valid || $snapshot_invalid)) { $Log->exit(); NATE::Exceptions::Argument->throw( "Either of snapshot_valid or snapshot_invalid" . " and snapshot_exclude cannot be passed together. snapshot_exclude" . " cannot be combined with snapshot_valid and/or snapshot_invalid ." ); } my $construct_vol_obj = sub { my %opts = @_; my $vol_number = $opts{vol_number}; my $ci; if ($vol_number =~ /volume2/) { $ci = $opts{volume2_command_interface} || $opts{command_interface}; } else { $ci = $opts{command_interface}; } if (!$opts{$vol_number . '_vserver'} && $ci->mode() eq 'CMode') { $Log->exit(); NATE::Exceptions::Argument->throw('Missing ' . $vol_number . '_vserver. ' . $vol_number . '_vserver is' . ' required if ' . $vol_number . ' represents volume name and not' . ' volume object'); } my $vol_obj = NACL::STask::Volume->new( command_interface => $ci, 'volume' => $opts{$vol_number}, 'vserver' => $opts{$vol_number . '_vserver'}, ); return $vol_obj; }; my %common_opts; $pkg->_copy_common_component_params( source => \%opts, target => \%common_opts ); ## Create volume objects foreach my $vol (@{$volume}) { if (!$vol->isa('NACL::C::Volume')) { if (!$second_vol) { $opts{volume1} = $vol; $vol = $construct_vol_obj->(%opts, vol_number => 'volume1'); } else { $opts{volume2} = $vol; $vol = $construct_vol_obj->(%opts, vol_number => 'volume2'); } } my $ci = $opts{command_interface}; if ( $second_vol) { $ci = $opts{volume2_command_interface} || $opts{command_interface}; } @snapshot = NACL::CS::VolumeSnapshot->fetch( command_interface => $ci, filter => {'volume' => $vol, 'vserver' => $vol->vserver()}, requested_fields => ['snapshot'], allow_empty => 1, %common_opts, ); if ($second_vol) { $vol2_obj = $vol; $set2 = NACL::MTask::Set::SnapshotListCompare->new( members => \@snapshot); } else { $vol1_obj = $vol; $set1 = NACL::MTask::Set::SnapshotListCompare->new( members => \@snapshot); } $second_vol = 1; } foreach my $snaplist ($snapshot_valid, $snapshot_invalid, $snapshot_exclude) { foreach my $snap (@{$snaplist}) { if (ref($snap) && !( $snap->isa('NACL::C::VolumeSnapshot') || $snap->isa('NACL::CS::VolumeSnapshot') ) ) { $Log->exit(); NATE::Exceptions::Argument->throw( ref($snap) . ' is passed in ' . "the snapshot list. It should have been provided as a " . 'NACL::(C|CS|STask)::VolumeSnapshot or just snapshot name' ); } if ($snap->isa('NACL::C::VolumeSnapshot')) { $snap = $snap->snapshot(); } # New NACL::CS::VolumeSnapshot object is created if $snap is a name # or NACL::C::VolumeSnapshot object if (!ref($snap)) { $snap = NACL::CS::VolumeSnapshot->new( command_interface => $opts{command_interface}, 'snapshot' => $snap, ); } } } my @set = ($set1, $set2); if (@$snapshot_valid) { my $i = 0; foreach my $vol (@{$volume}) { foreach my $snap (@{$snapshot_valid}) { if (!($set[$i])->contains($snap)) { my $volume_name = $vol->volume(); if (exists $vol_snap{$snap->snapshot() . ' not found'}) { $volume_name .= ' , ' . $vol_snap{$snap->snapshot() . ' not found'}; } $vol_snap{$snap->snapshot() . ' not found'} = $volume_name; } } $i++; } } if (@$snapshot_invalid) { my $i = 0; foreach my $vol (@{$volume}) { foreach my $snap (@{$snapshot_invalid}) { my $res = ($set[$i])->contains($snap); if (($set[$i])->contains($snap)) { my $volume_name = $vol->volume(); if (exists $vol_snap{$snap->snapshot() . ' found'}) { $volume_name .= ' , ' . $vol_snap{$snap->snapshot() . ' found'}; } $vol_snap{$snap->snapshot() . ' found'} = $volume_name; } } $i++; } } if (%vol_snap) { if ($nacltask_need_exception) { $Log->exit(); NACL::Exceptions::VerifyFailure->throw( "Snapshot does (not) exist on volume(s) provided", unexpected_values => {%vol_snap}); } else { $Log->trace( "Snapshot does (not) exist on volume(s) provided. Check" . " the following hash for the mismatched entries\n" . Dumper(\%vol_snap)); $Log->exit(); return 0; } } elsif (@$snapshot_invalid || @$snapshot_valid) { $Log->exit(); return 1; } # Compare two volumes to check if they have same set of snapshots if ( scalar @{$opts{volume}} == 2 && !@$snapshot_valid && !@$snapshot_invalid) { if ($snapshot_exclude) { # Remove snapshots from the list of snapshots my $i = 0; foreach my $vol (@{$volume}) { foreach my $snap (@{$snapshot_exclude}) { $set[$i]->remove($snap); } $i++; } } if ($set1->is_equal($set2)) { $Log->trace('Snapshots match between volumes.'); $Log->exit(); return 1; } else { my $diff1 = $set1->difference($set2); $diff1 = $diff1->members(); my $diff2 = $set2->difference($set1); $diff2 = $diff2->members(); my %diff = (%$diff1, %$diff2); # Save only volume name from snapshot hash foreach my $key (keys %diff) { $diff{$key} = $diff{$key}->{volume}; } if ($nacltask_need_exception) { $Log->exit(); NACL::Exceptions::VerifyFailure->throw( "Snapshots does not match" . " between volumes. Find difference between snapshot list" . " and associated volume in unexpected_values hash", unexpected_values => {%diff} ); } else { $Log->trace('Snapshots does not match between volumes. Extra ' . 'snapshots/mismatched snapshots are ' . Dumper(\%diff)); $Log->exit(); return 0; } } } } =head2 common_snapshots # Get the snapshots common between $volume and $volume2 # ($volume2 is also an object): @common_snapshots = $volume->common_snapshots( destination_volume => $volume2 ); # As above, but return objects instead of names: @common_snapshots = $volume->common_snapshots( destination_volume => $volume2, objects => 1 ); # Get common snapshots between two arbitrary volumes: @common_snapshots = NACL::STask::Volume->common_snapshots( command_interface => $src_ci, vserver => 'vs0', volume => 'src_volume', destination_ci => $dst_ci, destination_vserver => 'vs0', destination_volume => 'dst_volume' ); # Static call with two volume objects: @common_snapshots = NACL::STask::Volume->common_snapshots( volume => $src_volume, destination_volume => $dst_volume ); (Instance or class method) CMode CLI. Interrogates both volumes for lists of snapshots, then returns a list of those snapshots common to both volumes based on the C field of the snapshots. The return value is a list (possibly empty) of the snapshot names, or a list of B objects if the C option is given. The returned names or objects are those found on the source (calling) volume. To get the names or objects from the perspective of the second (destination) volume, call this method on that volume instead. =over =item Options The following options are recognized, in addition to the basic options for specifying a volume (C, C, C): =over =item C<< destination_ci =E $command_interface >> The command interface to use in looking up the list of snapshots on the destination volume. If this is not passed, then the source volume's command interface will be used. =item C<< destination_vserver =E $vserver_name >> The Vserver to use when looking up the list of snapshots for the destination volume. If not passed, then the Vserver used for the source volume will be used. =item C<< destination_volume =E $volume >> The name of the destination volume, or an object of either the B or B classes. If this value is an object, then the object is used to get the command interface and vserver to use (and the C and C options are ignored). =item C<< objects =E 0|1 >> If passed and a true value, then the returned list will be objects of the B class. Otherwise the values will be just the snapshot names. The default is 0 (false). =back If this method is called statically then it will be necessary to provide the information for the source volume via the C, C and C options. Like with the C option, if C is an object (B or B) then it overrides any C and/or C options with the ones from the object. =item Exceptions If an invalid argument is passed in, an exception of the class B will be thrown. Also, any exceptions that can be thrown from a component state B call are possible. =back =over =item Exceptions =over =item C This type of exception is thrown when any one of the parameters (volume, vserver and command_interface ) for both source and destination volume are missing. =back =back =cut ############################################################################### # # Sub Name: common_snapshots # # Description: Examine the lists of snapshots on both the calling volume # and the target ("destination") volume. Return a list of # those that are common between the two volumes, as # determined by the UUIDs of the snapshots themselves. # # Testing note: Tests for this subroutine are in the file # lib/NACL/UnitTest/STask/Snapmirror_cmode.thpl. This is # because I need a snapmirror relationship to transfer the # snapshots between volumes and I didn't want to add a # dependency on NACL::STask::Snapmirror to the test suite # lib/NACL/UnitTest/STask/Volume.thpl (or VolumeSnapshot). # # Arguments: NAME IN/OUT TYPE DESCRIPTION # $pkg_or_obj in ref Object of this class, or the # class name if static # @opts in array Remaining opts, handled by # _common_validate_with # # Returns: Success: list of common snapshots, possibly empty # Failure: throws exception # ############################################################################### sub common_snapshots { $Log->enter(); my ($pkg_or_obj, @opts) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@opts, additional_spec => { objects => {type => BOOLEAN, default => 0}, destination_ci => { type => OBJECT, isa => 'NACL::C::CommandInterface::ONTAP', optional => 1, }, destination_vserver => {type => SCALAR, optional => 1}, destination_volume => { type => SCALAR | OBJECT, callbacks => { 'valid object' => sub { my $opt = shift; # We're only checking that *if* this is a ref, it's # a ref to a Volume object: return 0 if (ref($opt) && !$opt->isa('NACL::C::Volume')); return 1; }, }, }, } ); my (@common, %uuid_map, @snapshots, $source_ci, $source_vserver, $source_volume, $dest_ci, $dest_vserver, $dest_volume ); # Make sure we have all the information we need for both the source and # destination sides of the equation: if (ref($opts{volume})) { $source_volume = $opts{volume}->volume; $source_vserver = $opts{volume}->vserver; $source_ci = $opts{volume}->command_interface; } else { $source_volume = $opts{volume}; $source_vserver = $opts{vserver}; $source_ci = $opts{command_interface}; } if (!($source_ci && $source_vserver && $source_volume)) { $Log->exit(); NATE::Exceptions::Argument->throw( 'Either the "volume" option should be an object, or all of ' . 'volume, vserver and command_interface must be specified'); } # Destination: if (ref($opts{destination_volume})) { $dest_volume = $opts{destination_volume}->volume; $dest_vserver = $opts{destination_volume}->vserver; $dest_ci = $opts{destination_volume}->command_interface; } else { $dest_volume = $opts{destination_volume}; $dest_vserver = $opts{destination_vserver}; $dest_ci = $opts{destination_ci}; } if (!($dest_ci && $dest_vserver && $dest_volume)) { $Log->exit(); NATE::Exceptions::Argument->throw( 'Either the "destination_volume" option should be an object, or ' . 'all of destination_volume, destination_vserver and ' . 'destination_ci must be specified'); } # Start by getting the snapshots on the source end @snapshots = NACL::CS::VolumeSnapshot->fetch( command_interface => $source_ci, filter => { vserver => $source_vserver, volume => $source_volume, }, requested_fields => [qw(snapshot instance-uuid)], allow_empty => 1 ); # Convert the list of snapshots to the hash %uuid_map for my $state (@snapshots) { $uuid_map{$state->instance_uuid} = $state->snapshot; } # Now get the list of snapshots on the destination end @snapshots = NACL::CS::VolumeSnapshot->fetch( command_interface => $dest_ci, filter => { vserver => $dest_vserver, volume => $dest_volume, }, requested_fields => [qw(snapshot instance-uuid)], allow_empty => 1 ); # For each record in @snapshots, if the UUID matches one we have in # %uuid_map, then that is a common snapshot. Put the name in @common. for my $state (@snapshots) { if (my $exists = $uuid_map{$state->instance_uuid}) { push(@common, $exists); } } if ($opts{objects}) { # Convert the elements of @common to objects: for my $elem (@common) { $elem = NACL::STask::VolumeSnapshot->new( command_interface => $source_ci, snapshot => $elem, vserver => $source_vserver, volume => $source_volume ); } } $Log->exit(); return @common; } =head2 shared_create $Shared_Vol = NACL::STask::Volume->shared_create( command_interface => $command_interface, volume => qr/$volume/i, type => { 'or' => ['RW', 'DP', 'DC'] }, concurrency => { 'not => ['low', 'medium']}, size => { '>=' => 50, '<' => 512 }, space-guarantee => { or => ['none', 'volume'], nacltask_cleanup_manager => $object, ); (Class method) Creates a shared volume prefixed with 'VL'. Instead of purge use 'shared_undo' to release the shared volume. The 'shared_undo' method is registered for cleanup automatically, but it needs to be invoked using the cleanup manager's run method. Current limitation is, the criteria can be specified for only those attributes which will hold (in the state class) the scalar or arrayref as their values. The criteria has to be either logical or relational and the supported ones are, =over =item Logical "or", "not" Supports only these type of logical criteria. =over Example: raidtype => { 'or' => ['raid4', 'raid_dp', ...], 'not' => ['raid3', ...] } Multiple conditions for the same attribute gets "AND"ed to ensure criteria match. =back =item Relational [==, <, <=, >, >= !=] =over Example: size => {">" => 1415577600} =back =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =back =cut =head2 recyclable_create $Recycl_Vol = NACL::STask::Volume->recyclable_create( command_interface => $command_interface, volume => qr/$volume/i, type => { 'or' => ['RW', 'DP', 'DC'] }, concurrency => { 'not => ['low', 'medium']}, size => { '>=' => 50, '<' => 512 }, space-guarantee => { or => ['none', 'volume'], nacltask_cleanup_manager => $object, ); (Class method) Creates a recyclable volume prefixed with 'vl'. Instead of purge use 'recyclable_undo' to release the volume. The 'recyclable_undo' method is registered for cleanup automatically, but it needs to be invoked using the cleanup manager's run method. Current limitation is, the criteria can be specified for only those attributes which will hold (in the state class) the scalar or arrayref as their values. The criteria has to be either logical or relational and the supported ones are, =over =item Logical "or", "not" Supports only these type of logical criteria. =over Example: raidtype => { 'or' => ['raid4', 'raid_dp', ...], 'not' => ['raid3', ...] } Multiple conditions for the same attribute gets "AND"ed to ensure criteria match. =back =item Relational [==, <, <=, >, >= !=] =over Example: size => {">" => 1415577600} =back =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =back =cut =head2 shared_undo $Shared_Vol = NACL::STask::Volume->shared_undo( command_interface => $command_interface, volume => qr/$volume/i, type => { 'or' => ['RW', 'DP', 'DC'] }, concurrency => { 'not => ['low', 'medium']}, size => { '>=' => 50, '<' => 512 }, space-guarantee => { or => ['none', 'volume'], ); (Class method) Reverts the volume to its shared state by reinitializing its contained element. The freed shared volume is prefixed by '_VL'. Current limitation is, the criteria can be specified for only those attributes which will hold (in the state class) the scalar or arrayref as their values.Resource name provided will be matched with the free shared resources available and if found, will be undone. The criteria has to be either logical or relational and the supported ones are, =over =item Logical "or", "not" Supports only these type of logical criteria. =over Example: raidtype => { 'or' => ['raid4', 'raid_dp', ...], 'not' => ['raid3', ...] } Multiple conditions for the same attribute gets "AND"ed to ensure criteria match. =back =item Relational [==, <, <=, >, >= !=] =over Example: size => {">" => 1415577600} =back =back =cut =head2 recyclable_undo $Recycl_Vol = NACL::STask::Volume->recyclable_undo( command_interface => $command_interface, volume => qr/$volume/i, type => { 'or' => ['RW', 'DP', 'DC'] }, concurrency => { 'not => ['low', 'medium']}, size => { '>=' => 50, '<' => 512 }, space-guarantee => { or => ['none', 'volume'], ); (Class method) Reverts the volume to its recyclable state by reinitializing its contained element. The freed recyclable volume is prefixed by '_vl'. Current limitation is, the criteria can be specified for only those attributes which will hold (in the state class) the scalar or arrayref as their values. Resource name provided will be matched with the free recyclable resources available and if found, will be undone. The criteria has to be either logical or relational and the supported ones are, =over =item Logical "or", "not" Supports only these type of logical criteria. =over Example: raidtype => { 'or' => ['raid4', 'raid_dp', ...], 'not' => ['raid3', ...] } Multiple conditions for the same attribute gets "AND"ed to ensure criteria match. =back =item Relational [==, <, <=, >, >= !=] =over Example: size => {">" => 1415577600} =back =back =cut =head2 cleanup $Stask_obj->cleanup(); (Instance Only Method) Performs the registered cleanup operations for the element. =cut =head2 mkfile $vol_obj->mkfile(filename => $name, %other_opts); (Instance method only) This method is used to make files on a volume. It uses the volume name and the file-name provided to construct the file path (of the form /vol/$vol_name/$file_name). This method is a wrapper around L<< NACL::STask::Mkfile->mkfile|lib-NACL-STask-Mkfile-pm/mkfile >>. Therefore, this method accepts all of the options accepted by the C method. =over =item Options =over =item C<< filename => $filename >> (Required) This is the name of the file to be created. Note that this is the path to the file. The path to the file gets constructed internally based on the volume name and file name. =item Other options All of the options accepted by L<< NACL::STask::Mkfile->mkfile|lib-NACL-STask-Mkfile-pm/mkfile >> are accepted by this method as well. =back =back =cut # Don't be surprised that there's no code here, the code is really in # NACL::C::Volume. The above is a duplication of the documentation in the # component, though in this case I think it's worth it. =head2 vreport_fix() NACL::STask::Volume->vreport_fix( command_interface => $command_interface, 'volume' => $volume, 'vserver' => $vserver, ); or $volume_stask_obj->vreport_fix( ); (Class or instance method) This method is used to fix vreport discrepancy by updating VLDB tables. Supported only for CMode CLI. =over =item Options =over =item command_interface, apiset_must, apiset_should, volume, vserver etc. =back =back =cut =head2 find_suitable_aggregates_for_volume # Find all aggregates that can host a non-vserver-root volume my @aggregates = NACL::STask::Volume->find_suitable_aggregates_for_volume( command_interface => $ci, ); # Find all aggregates that can host a non-vserver-root volume of size 40MB my @aggregates = NACL::STask::Volume->find_suitable_aggregates_for_volume( command_interface => $ci, size => '40MB' ); # Find all aggregates that can host a vserver-root volume my @aggregates = NACL::STask::Volume->find_suitable_aggregates_for_volume( command_interface => $ci, vsroot => 'true' ); # Find all hybrid aggregates that can host a non-vserver-root volume my @aggregates = NACL::STask::Volume->find_suitable_aggregates_for_volume( command_interface => $ci, additional_filter => { hybrid => 'true' } ); # Any of the above calls can be made in scalar context, in which case # the method returns only the first aggregate (i.e. the one with the # most available space) my $aggregate = NACL::STask::Volume->find_suitable_aggregates_for_volume( command_interface => $ci, ); Finds aggregates that can be used to host a volume. When invoked in list context, an array of aggregates is returned, arranged in descending order of available space (i.e. the aggregate with the most available space is listed first to the aggregate with the least available space being listed last). When invoked in scalar context, it returns a single aggregate - the one with the most available space. If no aggregates can host the volume, then a L exception is thrown. The default criteria are: =over =item Exceptions =over =item C This type of exception is thrown when there are no aggregates present with sufficient space to host a volume. =back =back =cut sub find_suitable_aggregates_for_volume { $Log->enter(); my ($pkg, @args) = @_; my %opts = validate_with( params => \@args, spec => { size => { type => SCALAR|UNDEF, optional => 1, }, vsroot => { type => SCALAR, default => 'false', }, is_constituent => { type => BOOLEAN, default => 0, }, additional_filter => { type => HASHREF, default => {}, }, 'space-guarantee' => { type => SCALAR|UNDEF, optional => 1, }, command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface::ONTAP', }, candidate_aggregates => { type => ARRAYREF, default => [] }, # Deliberately left undocumented since it's meant for internal use # This is to be used to get the list of CS objects (which contain # the availsize of each of the suitable aggregates) _get_cs_objs => { type => ARRAYREF, optional => 1, }, }, # Pass through other options to StorageAggregate->fetch. # This would typically include the common options allow_extra => 1, ); my $size = delete $opts{size}; my $vsroot = delete $opts{vsroot}; my $filter = delete $opts{additional_filter}; my $is_constituent = delete $opts{is_constituent}; my $space_guarantee = delete $opts{'space-guarantee'}; my $candidate_aggrs = delete $opts{candidate_aggregates}; my $get_cs_objs = delete $opts{_get_cs_objs}; my $ci = $opts{command_interface}; my $is_vsroot = ($vsroot eq 'true'); my $ci_is_vsim = $ci->is_vsim(); unless (defined $size) { # From SN.0, the vserver root volume size default was bumped up # to 1 GB. See burt 591274 if ($ci->is_cmode() && !$ci_is_vsim && $is_vsroot) { $size = '1GB'; } elsif ($is_constituent) { if ($ci_is_vsim) { # Constituent volumes can be a minimum of 256 MB for vsims $size = '256MB'; } else { # And a minimum of 1TB for filers $size = '1TB'; } } else { $size = '20MB'; } } # Burt #654541, If user specified the 'space-guarantee' as 'none', # get the maximum size, no need to apply aggregate 'availsize' as filter if (!defined $space_guarantee || $space_guarantee ne 'none') { $filter->{availsize} = ">=$size"; } $filter->{'is-home'} = "true"; # If candidate aggregates are provided, then filter from within the # provided list. if (@$candidate_aggrs) { my @aggrs; foreach my $candidate_aggr (@$candidate_aggrs) { if (blessed ($candidate_aggr)) { if ($candidate_aggr->can('aggregate')) { push @aggrs, $candidate_aggr->aggregate(); } else { $Log->exit(); NATE::Exceptions::Argument->throw('All of the values ' . "specified through the 'candidate_aggregates' " . 'in the call to NACL::STask::Volume->' . 'find_suitable_aggregates_for_volume() should ' . 'have been aggregate objects, but one of them was ' . 'of type ' . ref $candidate_aggr ); } } else { push @aggrs, $candidate_aggr; } } $filter->{aggregate} = join '|', @aggrs; } my @canned_filters = ('online_not_root'); ## Select only mirrored aggregate for VServer root ## volumes on MCC enabled systems (BURT 994184) if ( $is_vsroot ) { my $mcc_cs = NACL::CS::Metrocluster->fetch(command_interface => $ci); # MCC enabled systems will have 'is-MCC' aggr attribute set to 'true' # set 'mirror' filter to 'true' if 'is-MCC' is set to 'true' $filter->{'mirror'} = 'true' if lc $mcc_cs->local_configuration_state() eq 'configured'; $filter->{'is-snaplock'} = 'false'; } my @aggregates; try { my @aggrs_cs = NACL::CS::StorageAggregate->fetch( requested_fields => [qw(availsize)], filter => $filter, canned_filters => \@canned_filters,, sort_descending => ['availsize'], %opts, ); @aggregates = map { $_->aggregate() } @aggrs_cs; if (defined $get_cs_objs) { @$get_cs_objs = @aggrs_cs; } } catch NACL::Exceptions::NoElementsFound with { my $exception = $_[0]; my $error = 'Could not find any aggregates that could be used ' . "to host the volume! Filter criteria listed below:\n" . $exception->text(); $Log->exit(); NACL::STask::Exceptions::NoAggregatesWithSufficientSpaceForVolume ->throw( text => $error, size => $size ); }; $Log->exit(); return wantarray ? @aggregates : $aggregates[0]; } =head2 purge_all_snapshots $volume->purge_all_snapshots(); NACL::STask::Volume->purge_all_snapshots( command_interface => $command_interface, volume => $volume, vserver => $vserver, "method-timeout" => $timeout, nacltask_snapshot_policy_reenable => $boolean, ); (Class or Instance method) 1. This method disables the snapshot policy if it is not already disabled. 2. After disabling the snapshot policy it deletes all existing snapshots on the given volume. Supports CMode CLI/ZAPI and 7Mode CLI =over =item Options =over =item C<< command_interface=>$command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume=>$volume_name >> (Required for class method, Not Applicable for instance method) Name of volume on which snapshots needs to be deleted. =item C<< vserver=>$vserver_name >> (Required for class method Not Applicable for instance method) Name of vserver containing volume. =item C<< nacltask_snapshot_policy_reenable => 0 | 1 >> (Optional, defaults to 0) A boolean value which specifies whether or not to reset the previous snapshot-policy which was configured for the volume in interest, before invoking this task method, Default value will be '0'. =item C<< "method-timeout"=>$timeout >> (Optional) How long in seconds to wait for the job to finish. =item C<< apiset_must=>$ruleset >> (Optional)See L =item C<< apiset_should=>$ruleset >> (Optional)See L =back =back =cut sub purge_all_snapshots { $Log->enter(); my $pkg_or_obj = shift; my $cs_pkg = $pkg_or_obj->get_CS_package_name(); my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { nacltask_snapshot_policy_reenable => {type => BOOLEAN, default => 0}, }, ); my $enable_snapshot_policy = 0; my (%task_opts, %common_opts, $policy_opts, $policy, $new_policy); $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts ); $pkg_or_obj->_move_nacltask_options( source => \%opts, target => \%task_opts, ); my $ci = $common_opts{'command_interface'}; $enable_snapshot_policy = delete $task_opts{'nacltask_snapshot_policy_reenable'}; my $primary_keys = $pkg_or_obj->get_primary_keys_options(%opts); my $snapshot_policy = $cs_pkg->fetch(%common_opts, filter => $primary_keys,); if ($ci->is_cmode()) { $policy = $snapshot_policy->snapshot_policy(); $new_policy = "none" if ($policy ne "none"); } else { $policy = $snapshot_policy->nosnap(); $new_policy = "none" if ($policy ne "on"); } $policy_opts = $pkg_or_obj->get_primary_keys_options(%opts); $policy_opts->{'snapshot-policy'} = $new_policy; if ($new_policy) { $pkg_or_obj->modify(%common_opts, %{$policy_opts}); } try { $opts{'snapshot'} = "*"; NACL::C::Snapshot->delete(%common_opts, %opts); } catch NACL::APISet::Exceptions::NoMatchingEntriesException with { my $ex = shift; $Log->debug("Snapshots are not available under specified volume"); }; $policy = 'none' if ($policy eq 'on' && $ci->is_7mode); if ($enable_snapshot_policy && $policy !~ /^none$/i) { $policy_opts->{'snapshot-policy'} = $policy; $pkg_or_obj->modify(%common_opts, %{$policy_opts}); } $Log->exit(); } ## end sub purge_all_snapshots =head2 autosize_wait $volume->autosize_wait(mode => $mode); NACL::STask::Volume->autosize_wait( command_interface => $command_interface, volume => $volume, wait_for => 'grow', original_size => $orig_size, nacltask_verify => $verify_boolean expected_size => $expected_size, "method-timeout" => $timeout, polling_interval => $interval, ); (Class or Instance method) This method waits until the volume size has completed autosize, whether growing to increase in size or shrinking to return unused blocks. It will throw an exception if the specified timeout period passes before completion. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for instance method) Name of volume under test. =item C<< wait_for => "grow" | "shrink" >> (Required) The action to wait for, either "grow" or "shrink" =item C<< original_size => $size >> (Required) The size of the volume before autosize began. Takes the form of any number convertable by L. Units default to bytes. =item C<< nacltask_verify => $nacltask_verify_boolean >> (Optional) If '0' (default), the method will only wait until the volume has grown or shrunk independent of what the actual size becomes. If '1', verification will ensure the volume grew/shrunk to the expected size. If set, to '1', expected_size must be set as well. =item C<< expected_size => $size >> (Optional) The expected size of the volume after autosize occurs. Takes the form of any number convertable by L, e.g. "20MB", "20480KB". Units default to bytes. Required if nacltask_verify is set to 1. =item C<< "method-timeout" => $timeout >> (Optional) How long in seconds to wait for the job to finish. Default is 300. =item C<< polling_interval => $interval >> (Optional) Interval time period to wait before checking for autosize changes. Default is 5. =back =back =over =item Exceptions =over =item C This type of exception is thrown when verification for volume autosize has failed. =item C This type of exception is thrown when an invalid value passed as action to be wait for or value for expected_size is missing. =item C< NACL::Exceptions::Timeout> This type of exception is thrown when volume autosize has not happened with in specified $timeout seconds. =back =back =cut sub autosize_wait { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { wait_for => {type => SCALAR}, original_size => {type => SCALAR}, nacltask_verify => {type => BOOLEAN, default => 0}, expected_size => {type => SCALAR, optional => 1}, 'method-timeout' => {type => SCALAR, default => 300}, polling_interval => {type => SCALAR, default => 5}, }, ); my ($ci, $timeout, $original_size, $volume, $mode, $original_size_bytes, $verify, $wait_for, $expected_size, $polling_interval, $expected_size_bytes, ); $ci = $opts{'command_interface'}; $timeout = $opts{'method-timeout'}; $wait_for = $opts{wait_for}; $verify = $opts{nacltask_verify}; $original_size = $opts{original_size}; $expected_size = $opts{expected_size}; $polling_interval = $opts{polling_interval}; $volume = $opts{volume}; if ($wait_for =~ /^grow$/i) { $mode = 1; } elsif ($wait_for =~ /^shrink$/i) { $mode = 0; } else { $Log->exit(); NATE::Exceptions::Argument->throw( "Invalid value, \"$wait_for\", for wait_for. " . "Should be either \"grow\" or \"shrink\""); } # Needed for calling other STask methods my %common_opts_with_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_with_ci, ); $common_opts_with_ci{volume} = $volume; $original_size_bytes = NACL::C::UnitNormalization::convert_to_bytes($original_size); # There needs to be a method to determine the expected size. Targeted # for future STask development if ($verify) { if ( ! defined $expected_size) { $Log->exit("Must specifiy expected_size when using " . "nacltask_verify"); NATE::Exceptions::Argument->("Must specify expected_size " . "when using nacltask_verify"); } $expected_size_bytes = NACL::C::UnitNormalization::convert_to_bytes($expected_size); } # Give some time for counters to update after file reservation disable $Log->debug( "Poll for up to $timeout seconds for the volume $volume to autosize"); my $endtime = time() + $timeout; my $resp; my $changed = 0; my $new_size; while (time() < $endtime && $changed == 0) { $new_size = $pkg_or_obj->sum_total_size(key => "total", %common_opts_with_ci); $Log->debug("Verify the volume size. New size is '$new_size'."); if ($verify) { # Verify the volume is the expected size # Allow a 2% threshold for success -- this will be revisited $changed = 1 if ( abs($new_size - $expected_size_bytes) < ($new_size * .02)); } else { if ($mode == 1) { $changed = 1 if ($new_size > $original_size_bytes); } else { $changed = 1 if ($new_size < $original_size_bytes); } } Tharn::snooze($opts{polling_interval}); } # The two exits from the above loop are timeout OR autosize occurred. # If the latter is not true, then it must have been a timeout if (!$changed) { $Log->exit(); my $msg = "Volume " . $opts{volume} . " did not $wait_for "; if ($verify) { $msg .= " to $expected_size"; } $msg .= ". After the specified timeout of $timeout seconds, the "; $msg .= "volume is of size " . $new_size; NACL::Exceptions::Timeout->throw($msg); } $Log->exit(); } ## end sub autosize_wait =head2 sum_total_size $volume->sum_total_size(key => $key); $scalar = NACL::STask::Volume->sum_total_size( command_interface => $command_interface, volume => $volume, key => 'total', ); %hash = NACL::STask::Volume->sum_total_size( command_interface => $command_interface, volume => $volume, key => 'all', ); (Class or Instance method) This method gets the total size of the volume (usable space + snapshot space). Can be used to obtain any of the measurable spaces in the volume depending upon the value of $key, including the total, used, reserved, or available space. If desired, a hash of all the values can be returned by setting $key to "all". =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for instance method) Name of volume under test. =item C<< key => "total" | "used" | "reserved" | "avail" | "all" >> (Optional) The space measurement to return. The default is total. A reference to a hash is returned if $key is set to "all", otherwise a scalar. Values are measured in Bytes. =back =back =cut sub sum_total_size { $Log->enter(); my $pkg_or_obj = shift; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => {key => {type => SCALAR, default => "total"},}, ); my $ci = $opts{command_interface}; my $volume_name = $opts{volume}; # There is support for df in CMode, but it's a hidden command so it's not # (yet) supported at the component layer. Using the nodescope APISet # allows us to use simpler code. my $apiset = $ci->get_7m_or_nodescope_apiset(); my $resp = $apiset->df( volume => 1, name => $volume_name, 'reserved-space' => 1, "scale-kilobytes" => 1 )->get_parsed_output(); # We're using scale-kilobytes, and this provides the "KB" suffix # but we need to convert it to bytes foreach my $line (@{$resp}) { foreach my $index ('total', 'used', 'avail', 'reserved') { $line->{$index} = NACL::C::UnitNormalization::convert_to_bytes( $line->{$index}); } } # Since we need the sum total volume size, adding the AFS + snapshot my $total = $resp->[0]->{total} + $resp->[1]->{total}; my $avail = $resp->[0]->{avail} + $resp->[1]->{avail}; my $reserved = $resp->[0]->{reserved} + $resp->[1]->{reserved}; my $used; if ($resp->[1]->{used} > $resp->[1]->{total}) { # If snapshot spill has occurred (i.e. snapshot usage > 100%), then # the space will also show up in the AFS. Don't double count it. $used = $resp->[0]->{used} + $resp->[1]->{total}; } else { $used = $resp->[0]->{used} + $resp->[1]->{used}; } # Could check if key is one of the four fields, but caveat emptor for now if ($opts{key} eq 'all') { my $ret_hash = {}; foreach my $index ('total', 'used', 'avail', 'reserved') { $ret_hash->{$index} = $resp->[0]->{$index} + $resp->[1]->{$index}; } $Log->exit(); return $ret_hash; } else { my $ret_value; $ret_value = $resp->[0]->{$opts{key}} + $resp->[1]->{$opts{key}}; $Log->exit(); return $ret_value; } } ## end sub sum_total_size =head2 default_grow_threshold $grow_threshold = $volume->default_grow_threshold( ); $grow_threshold = NACL::STask::Volume->default_grow_threshold( command_interface => $command_interface, volume => $volume, ); (Class or Instance method) This method returns the default grow threshold percentage of the volume using the algorithm devised by product development. This value will not be the actual grow threshold if the threshold values for the volume have been changed. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for instance method) Name of volume under test. =back =back =cut sub default_grow_threshold { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with(params => \@args,); my $flag; my $ci = $opts{command_interface}; my $volsize = $pkg_or_obj->sum_total_size(key => "total"); my $volsize_inkb = ceil($volsize / 1024); $Log->comment("size in KB is " . $volsize_inkb); # Get the threshold using the appropriate wafl_reclaim_threshold flag if ($volsize_inkb <= 20971520) { $flag = 'wafl_reclaim_threshold_t'; } elsif ($volsize_inkb <= 104857600) { $flag = 'wafl_reclaim_threshold_s'; } elsif ($volsize_inkb <= 524288000) { $flag = 'wafl_reclaim_threshold_m'; } elsif ($volsize_inkb <= 1048576000) { $flag = 'wafl_reclaim_threshold_l'; } else { $flag = 'wafl_reclaim_threshold_xl'; } my $api_set = $ci->get_7m_or_nodescope_apiset(); my $threshold = $api_set->printflag( "flags" => $flag, 'privilege-level' => 'test' )->get_parsed_output()->[0]->{$flag}; my $grow = hex($threshold); $Log->comment("default grow threshold is $grow"); $Log->exit(); return $grow; } ## end sub default_grow_threshold =head2 default_shrink_threshold $shrink_threshold = $volume->default_shrink_threshold( ); $shrink_threshold = NACL::STask::Volume->default_shrink_threshold( command_interface => $command_interface, volume => $volume, ); (Class or Instance method) This method returns the default shrink threshold percentage of the volume using the algorithm devised by product development. This value will not be the actual shrink threshold if the threshold values for the volume have been changed. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for instance method) Name of volume under test. =back =back =cut sub default_shrink_threshold { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with(params => \@args,); my $shrink; my $threshold = $pkg_or_obj->default_grow_threshold(%opts); if ($threshold > 50) { $shrink = 50; } elsif ($threshold > 15) { $shrink = $threshold - 15; } else { $shrink = 0; } $Log->comment("default shrink threshold is $shrink"); $Log->exit(); return $shrink; } ## end sub default_shrink_threshold =head2 mount # Class method # Ordinary form of call NACL::STask::Volume->mount( command_interface => $node, volume => $vol_name, vserver => $vs_name, 'junction-path' => $path, nacltask_already_mounted => 'remount' | 'die' # Optional, defaults to "remount" nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, ); # Instance method # Ordinary form of call $vol_obj->mount( 'junction-path' => $path, nacltask_already_mounted => 'remount' | 'die' # Optional, defaults to "remount" ); # Class method # Advanced form of call (junction-path not passed) NACL::STask::Volume->mount( command_interface => $node, volume => $vol_name, vserver => $vs_name, ); # Instance method # Advanced form of call (junction-path not passed) $vol_obj->mount(); (Class or instance method) Mounts the volume. There are two forms in which the method can be called: =over =item Standard form In this form, we explicitly state the C where we want the volume to be mounted. In this form, a new C argument is accepted through which we can specify what to do if the volume is already mounted, but at a different junction-path. The default is "remount", which means that it will unmount the volume and remount it at the junction-path provided by the caller. This way, regardless of whether the volume was originally mounted or not, it will end up being mounted at the C provided by the caller. The other value accepted for the C argument is C (exception out if mounted at a different path). =item Advanced form In this form, the C is not provided. If called this way, then the method does this: * If the volume is already mounted, it leaves it mounted at that junction-path. * If not already mounted, the method chooses a junction-path (currently it is "/$volname") and mounts it there. Note that we should B assume that the volume is mounted at "/$volname". The correct way to determine where the volume got mounted (or remained mounted at, as the case may be) is by invoking get_one_state_attribute (or state): $vol_obj->mount(); my $path = $vol_obj->get_one_state_attribute('junction-path'); =back =over =item Options =over =item C<< nacltask_already_mounted => 'remount' | 'die' >> (Optional, defaults to "remount", ignored if using the advanced form) What to do if the volume is already mounted but at a different junction-path. The default is C, which means that it will unmount and remount at the C provided by the caller. If the value is specified as C, then a L exception is thrown. This argument is ignored in the advanced form of the method call. =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =item Other options The other options accepted by this method are documented in L =back =back =over =item Exceptions =over =item C< NACL::C::Exceptions::Volume::AlreadyMounted> This type of exception is thrown when there is an attempt to mount a volume which is already mounted. =back =back =cut sub mount { $Log->enter(); my ($pkg_or_obj, @args) = @_; my $callbacks = { "Value of 'nacltask_already_mounted' should be either " . "'remount' or 'die'" => sub { $_[0] =~ /^(die)|(remount)$/ } }; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { nacltask_already_mounted => { type => SCALAR, default => 'remount', callbacks => $callbacks }, $pkg_or_obj->_cleanup_validate_spec(), }, allow_extra => 1 ); my $already_mounted = delete $opts{nacltask_already_mounted}; my ( %opts_for_cleanup, $nacltask_to_cleanup ); $pkg_or_obj->_copy_common_opts_for_cleanup( 'source' => \%opts, 'target' => \%opts_for_cleanup, 'nacltask_to_cleanup' => \$nacltask_to_cleanup, 'to_cleanup' => 'unmount' ); my $generated_junction_path; if (!exists $opts{'junction-path'}) { $opts{'junction-path'} = $pkg_or_obj->_determine_junction_path(volume => $opts{volume}); $generated_junction_path = 1; } AGAIN: { use warnings 'exiting'; try { $pkg_or_obj->SUPER::mount(%opts); } catch NACL::C::Exceptions::Volume::AlreadyMounted with { my $exception = $_[0]; if ($generated_junction_path) { # Ignore error if already mounted } elsif ($already_mounted =~ /remount/i) { my %unmount_opts; $pkg_or_obj->_copy_primary_keys( source => \%opts, target => \%unmount_opts ); $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%unmount_opts ); $pkg_or_obj->unmount(%unmount_opts); no warnings 'exiting'; redo AGAIN; } else { $Log->exit(); $exception->throw(); } }; } # Register this resource with the Cleanup Manager for cleanup $pkg_or_obj->_register_for_cleanup(%opts_for_cleanup) if ($nacltask_to_cleanup); $Log->exit(); } # Currently as simple as "/$vol_name", but I've made this a standalone # method so that if/when this is updated to be more complicated, then # multiple pieces of code do not need to be updated. sub _determine_junction_path { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = validate_with( params => \@args, spec => {volume => {type => SCALAR}} ); $Log->exit(); return "/$opts{volume}"; } =head2 unmount L<< NACL::STask::STask->purge_method_builder(c_method_name => 'unount')|lib-NACL-STask-STask-pm/purge_method_builder >> =cut __PACKAGE__->purge_method_builder(stask_method_name => 'unmount', c_method_name => 'unmount'); =head2 parents @parents = $volume_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(); $Log->exit(); return qw(NACL::STask::Vserver); } =head2 online NACL::STask::Volume->online( command_interface => $ci, volume => $vol_name, vserver => $vserver_name, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs %other_opts ); or $vol_stask_obj->online( %other_opts, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs ); (Class or instance method) Onlines a volume. This method provides additional services beyond what's inherent to the product's volume online commands, as controlled and described by C and C options. The timeout is defaulted to 300 seconds. =over =item Options =over =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =item C<< nacltask_wait => 0 | 1 >> (Optional, defaults to 1, applicable only if an async ZAPI was used) If we're restricting the infinite volume using ZAPI, then an "async" ZAPI is used. The "async" ZAPI returns immediately and starts a background job. If set to 1 (the default), the method will wait on the job. Note that this is applicable only for infinite volumes through ZAPI: in all other cases the operation does not start a job. =item Other options All of the other options accepted by L. =back =back =cut # Definition is from _AggregateVolume. =head2 offline NACL::STask::Volume->offline( command_interface => $ci, volume => $vol_name, vserver => $vserver_name, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs %other_opts ); or $vol_stask_obj->offline( %other_opts, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs ); (Class or instance method) Offlines a volume. This method provides additional services beyond what's inherent to the product's volume offline commands, as controlled and described by C and C options. The timeout is defaulted to 300 seconds. =over =item Options =over =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =item C<< nacltask_wait => 0 | 1 >> (Optional, defaults to 1, applicable only if an async ZAPI was used) If we're restricting the infinite volume using ZAPI, then an "async" ZAPI is used. The "async" ZAPI returns immediately and starts a background job. If set to 1 (the default), the method will wait on the job. Note that this is applicable only for infinite volumes through ZAPI: in all other cases the operation does not start a job. =item Other options All of the other options accepted by L. =back =back =cut # Definition is from _AggregateVolume. =head2 restrict NACL::STask::Volume->restrict( command_interface => $ci, volume => $vol_name, vserver => $vserver_name, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs %other_opts ); or $vol_stask_obj->restrict( %other_opts, nacltask_to_cleanup => $boolean, nacltask_cleanup_manager => $object, nacltask_wait => 0 | 1 # Applicable only for async ZAPIs ); (Class or instance method) Restricts a volume. This method provides additional services beyond what's inherent to the product's volume restrict commands, as controlled and described by C and C options. The timeout is defaulted to 300 seconds. =over =item Options =over =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to use for registering. Default : Will use the default cleanup manager. =item C<< nacltask_wait => 0 | 1 >> (Optional, defaults to 1, applicable only if an async ZAPI was used) If we're restricting the infinite volume using ZAPI, then an "async" ZAPI is used. The "async" ZAPI returns immediately and starts a background job. If set to 1 (the default), the method will wait on the job. Note that this is applicable only for infinite volumes through ZAPI: in all other cases the operation does not start a job. =item Other options All of the other options accepted by L. =back =back =cut # Definition is from _AggregateVolume. # CMode allows setting the state to a value other than "online" at # creation time. This is not supported in 7Mode. We can provide the # same effect in 7Mode by doing: # $vol = Volume->create(); # # Need to wait before we can modify the state # $vol->wait_for_creation(); # $vol->modify(state => $state); # This function determines if we need to wait and then call modify for # 7Mode. sub _determine_if_need_to_modify_state_7m { $Log->enter(); my ($pkg, @args) = @_; my %opts = validate_with( params => \@args, spec => { state => { type => SCALAR | UNDEF }, is_cmode => { type => SCALAR }, } ); my $state = $opts{state}; # If not passed, default to "online" $state = 'online' unless defined $state; my $need_to_modify_state_7m; if (!$opts{is_cmode} && $state ne 'online') { # 7Mode: need to wait for the volume to get created before # we can modify its state (the state cannot be modified at # create time - this can only be done in CMode) $need_to_modify_state_7m = 1; } $Log->exit(); return $need_to_modify_state_7m; } # This task method is just a wrapper around the "rename" method of component # and it provides an option to register the rename operation for # cleanup # Note: If the cleanup is registered with STask instance, since the medhotd to # rename happened through component and component updates the newname on the instance, # Cleanup will work fine but the purge on that STask object will fail =head2 rename # Class method NACL::STask::Volume->rename( command_interface => $ci, volume => $vol_name, vserver => $vs_name, # Necessary for CMode nacltask_to_cleanup => $boolean, # Optional nacltask_cleanup_manager => $object, # Optional nacltask_wait => 0 | 1, # Applicable only for async ZAPI %other_opts ); or # Instance method $vol_obj->rename( nacltask_to_cleanup => $boolean, # Optional nacltask_cleanup_manager => $object, # Optional nacltask_wait => 0 | 1, # Applicable only for async ZAPI %other_opts ); (Class or instance method) Renames a volume. In addition to the facilities provided by the component, this provides support for cleanup and automatically waits for completion if an async ZAPI was used. The timeout is defaulted to 300 seconds. =over =item Options =over =item C<< nacltask_to_cleanup => 0 | 1 >> (Optional, defaults to 0) If set to 1, registers the rename for cleanup (the undo operation will be to rename to the original name) =item C<< nacltask_cleanup_manager => $cleanup_manager >> (Optional) The cleanup manager to use for registering the operation. If not specified, then the default cleanup manager is used. =item C<< nacltask_wait => 0 | 1 >> (Optional, defaults to 1, applicable only if an async ZAPI was used) If we're renaming the infinite volume using ZAPI, then an "async" ZAPI is used. The "async" ZAPI returns immediately and starts a background job. If set to 1 (the default), the method will wait on the job. Note that this is applicable only for infinite volumes through ZAPI: in all other cases the operation does not start a job. =item Other options See L<< NACL::C::Volume->rename|lib-NACL-C-Volume-pm/rename >> for all the other options accepted by this method. =back =back =cut sub rename { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = validate_with( params => \@args, spec => { nacltask_wait => { type => SCALAR, default => 1 } }, allow_extra => 1 ); my $wait = delete $opts{nacltask_wait}; my $dummy; $opts{job_component} ||= \$dummy; $pkg_or_obj->_rename(%opts, 'element' => 'volume'); if ($wait) { $pkg_or_obj->_wait_for_async_zapi(%opts); } $Log->exit(); } sub _validate_allowed_protocols_on_vservers_and_lif { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = validate_with( params => \@args, spec => { vserver_states => {type => ARRAYREF}, interface_states => {type => ARRAYREF}, requested_protocols => {type => ARRAYREF}, }, ); my $vserver_cs_objs = delete $opts{'vserver_states'}; my $lif_cs_objs = delete $opts{'interface_states'}; my $requested_protocols = delete $opts{'requested_protocols'}; my $valid_vservers = []; my $suitable_vserver_not_found = 1; my $suitable_interface_not_found = 1; my $is_a_subset = sub { my ($aref1, $aref2) = @_; my %array2_as_hash = map { $_ => 1 } @{$aref2}; my $count_of_protocols_matched; foreach my $protocol (@{$aref1}) { ++$count_of_protocols_matched if ($array2_as_hash{$protocol}); } if (scalar @{$aref1} == $count_of_protocols_matched) { return 1; } else { return 0; } }; my %vserver_lif_supported_protocols; foreach my $interface (@{$lif_cs_objs}) { my $data_protocol = $interface->data_protocol(); my $vserver = $interface->vserver(); my @protocols_of_lif = keys %{ { map { $_ => 1 } ( @{$vserver_lif_supported_protocols{$vserver}}, @{$data_protocol} ) } }; $vserver_lif_supported_protocols{$vserver} = \@protocols_of_lif; } foreach my $vserver (@{$vserver_cs_objs}) { my $allowed_protocols = $vserver->allowed_protocols(); my $vserver_name = $vserver->vserver(); if ($is_a_subset->($requested_protocols, $allowed_protocols)) { $suitable_vserver_not_found = 0; my $data_protocols = $vserver_lif_supported_protocols{$vserver_name}; if ($is_a_subset->($requested_protocols, $data_protocols)) { push @{$valid_vservers}, $vserver; $suitable_interface_not_found = 0; } } } @{$vserver_cs_objs} = @{$valid_vservers}; if ($suitable_vserver_not_found) { my $error = "No vservers found matching the specified protocols: \[" . join ",", @{$requested_protocols} . "\]"; $Log->exit(); NACL::STask::Exceptions::NoVserversWithSuitableAllowedProtocols ->throw(text => $error,); } elsif ($suitable_interface_not_found) { my $error = "No interfaces found matching the specified protocols: \[" . join ",", @{$requested_protocols} . "\]"; $Log->exit(); NACL::STask::Exceptions::NoInterfacesWithSuitableAllowedProtocols ->throw(text => $error,); } $Log->exit(); } =head2 space_comparison # Class method NACL::STask::Volume->space_comparison( command_interface => $command_interface, nacltask_check_snapspill => 1, volume => $volume, %other_options ); (or) $Vol_obj->space_comparison(); (Class and Instance method) Performs the output comparison for the volume using different commands(df, show-space, show-footprint) based on the nacltask_check_snapspill parameter. 1.Without snap_spill( nacltask_check_snapspill => 0) a. CS objects will be obtained for the volume show-space command to get the ""user_data", "filesystem_metadata", "inodes", "snapshot_reserve" attributes and for the volume command to get the "used" and "total(Snapshot total)" attributes. b. Compare the total of "used" and "total" obtained from "df" with the "total" value obtained from "volume show-space". Df used + snap total = Total (command output of volume show-space) If values are not equal then NACL::STask::ExceptionsSpaceComparisonFailure would be thrown. c. Compare the sum of "user_data", "filesystem_metadata", "inodes", and snapshot_reserve" to "total" value from the "volume show-space" command. Total Used(volume show-space) = User Data + Filesystem Metadata + Inodes + Snapshot Reserve If values are not equal then NACL::STask::Exceptions::SpaceComparisonFailure would be thrown. 1.With snap_spill( nacltask_check_snapspill => 1) a. CS objects will be obtained for the volume show-space command to obtain the 'total' and 'snapshot_spill' attributes and and for the volume command to obtain the "used(Snapshot used space)","total(Snapshot total space)" attributes. b. Compare the difference of the "used" and "total" attributes with "snapshot_spill" value of the volume show-space command. Df snap used – snap total = snapshot spill (command output of volume show-space) If values are not equal but the difference of these values are less than or equals the nacltask_threshold value then it will not return anything but if the values are beyond the threshold value then the comparison will perform again based on the nacltask_retry value. After retrying the comparison, if values are still not equal then it will throw an exception of type NACL::STask::Exceptions::SpaceComparisonFailure. c. Compare the sum of "used and "snapshot_used" to the sum of "total" and "snapshot_spill". Df used+ snap used = Total + snapshot spill (command output of volume show-space) If values are not equal but the difference of these values are less than or equals the nacltask_threshold value then it will not return anything but if the values are beyond the threshold value then the comparison will perform again based on the nacltask_retry value. After retrying the comparison, if values are still not equal then it will throw an exception of type NACL::STask::Exceptions::SpaceComparisonFailure. Supports Cmode CLI/ZAPI & 7Mode CLI =over =item Options =over =item C<< nacltask_check_snapspill => 0 | 1 >> (Optional) If 0 (the default), space comparison will be performed without considering snapshot-spill value. If 1, comparison will be performed by considering the "snapshot-spill" value. =item C<< nacltask_threshold => $threshold_limit >> (Optional, default => 1%) This option will provide the threshold limit for the values If the comparison got failed then atleast the difference of the values should not be beyond the thresholkd value. =item C<< nacltask_retry => $num_of_retries >> (Optional) If this option is provided, once the comparison failed between the values and the difference is beyond the threshold value then the comparison has to be done iteratively. The value for this option should be more than the default value '1'. If '1' (default), comparison of the values will iterate the loop for one time. =item C<< polling_interval => $interval >> (Optional) The interval, in seconds, the interval at which method has to wait for the next iteration of comparison. =item C<< user_data >> User Data, possible value(s) are [KB,MB,GB,TB,PB] Filled in for CMode CLI/ZAPI, 7Mode CLI/ZAPI. =item C<< snapshot_reserve >> Snapshot Reserve Size, possible value(s) are [KB,MB,GB,TB,PB] Filled in for CMode CLI/ZAPI, 7Mode CLI/ZAPI. =item C<< inodes >> Inode Metadata, possible value(s) are [KB,MB,GB,TB,PB] Filled in for CMode CLI/ZAPI, 7Mode CLI/ZAPI. =item C<< filesystem_metadata >> Filesystem Metadata, possible value(s) are [KB,MB,GB,TB,PB] Filled in for CMode CLI/ZAPI, 7Mode CLI/ZAPI. =item C<< total_used >> Used Including Snapshot Reserve, possible value(s) are [KB,MB,GB,TB,PB] Filled in for CMode CLI/ZAPI, 7Mode CLI/ZAPI. =item C<< used_including_snapshot_reserve >> Used Including Snapshot Reserve, possible value(s) are [KB,MB,GB,TB,PB] Filled in for CMode CLI, 7Mode/Nodescope CLI =item Other options All of the other options accepted by L. L. =back =back =over =item Exceptions =over =item C This exception is thrown when verification of field values does not pass, i.e the field value for one or more fields does not match what was provided. =back =back =cut sub space_comparison { $Log->enter(); my $pkg_or_obj = shift; my %opts = $pkg_or_obj->_common_validate_with( params => \@_, additional_spec => { nacltask_check_snapspill => {type => BOOLEAN, default => 1}, nacltask_threshold_value => {type => SCALAR, default => 1}, nacltask_retries => {type => SCALAR, default => 1}, polling_interval => {type => SCALAR, default => 10}, }, ignore_primary_keys => 1, allow_extra => 1, ); my $snapspill = delete $opts{nacltask_check_snapspill}; my $num_of_retries = 0; my $thresold_limit = delete $opts{nacltask_threshold_value}; my $max_retry = delete $opts{nacltask_retries}; my $polling_interval = delete $opts{polling_interval}; my ($failure_msg,$mismatched_value); if (!$snapspill) { my $hash; while ($num_of_retries <= $max_retry) { $hash = $pkg_or_obj->_get_cs_objects(%opts); if (_space_range_check_failed($hash->{sum_of_fields}, $hash->{total_used},$thresold_limit)) { $num_of_retries++; $Log->comment("\n As the calculated threshold value is ". "less than the specified threshold limit retrying to compare the values"); Tharn::snooze $polling_interval; } else { # If the threshold limit is beyond the calculated limit # then exit from the loop last; } } if ($num_of_retries > $max_retry) { $failure_msg .= sprintf( "\n%-20s%-30s\n", "Total_used", "Sum_of_all_fields" ); $failure_msg .= sprintf( "\n%-20s%-30s\n", $hash->{total_used} , $hash->{sum_of_fields} ); $mismatched_value->{total_used} = $hash->{total_used}; $mismatched_value->{sum_of_fields} = $hash->{sum_of_fields}; } $num_of_retries = 1; while ($num_of_retries <= $max_retry) { $hash = $pkg_or_obj->_get_cs_objects(%opts); if (_space_range_check_failed($hash->{total_used}, $hash->{total_space},$thresold_limit)) { $num_of_retries++; $Log->comment("\n As the calculated threshold value is ". "less than the specified threshold limit retrying to compare the values"); Tharn::snooze $polling_interval; } else { # If the threshold limit is beyond the calculated limit # then exit from the loop last; } } if ($num_of_retries > $max_retry) { $failure_msg .= sprintf( "\n%-20s%-30s\n", "Total_Space", "Total_Used" ); $failure_msg .= sprintf( "\n%-20s%-30s\n", $hash->{total_space} , $hash->{total_used} ); $mismatched_value->{df_used_including_snapshot} = $hash->{total_space}; $mismatched_value->{total_used} = $hash->{total_used}; } } else { my $hash; while ($num_of_retries <= $max_retry) { $hash = $pkg_or_obj->_get_cs_objects(%opts); if (_space_range_check_failed($hash->{snapshot_spill}, $hash->{snap_space},$thresold_limit)) { $num_of_retries++; $Log->comment("\n As the calculated threshold value is ". "less than the specified threshold limit retrying to compare the values"); Tharn::snooze $polling_interval; } else { # If the threshold limit is beyond the calculated limit # then exit from the loop last; } } if ($num_of_retries > $max_retry) { $failure_msg .= sprintf( "\n%-20s%-30s\n", "Snap_Space", "Snapshot_Spill" ); $failure_msg .= sprintf( "\n%-20s%-30s\n", $hash->{snap_space}, $hash->{snapshot_spill}); $mismatched_value->{snap_space} = $hash->{snap_space}; $mismatched_value->{snapshot_spill} = $hash->{snapshot_spill}; } $num_of_retries = 0; while ($num_of_retries <= $max_retry) { $hash = $pkg_or_obj->_get_cs_objects(%opts); if (_space_range_check_failed($hash->{snap_used}, $hash->{total_spill},$thresold_limit)) { $num_of_retries++; $Log->comment("\n As the calculated threshold value is ". "less than the specified threshold limit retrying to compare the values"); Tharn::snooze $polling_interval; } else { # If the threshold limit is beyond the calculated limit # then exit from the loop last; } } if ($num_of_retries > $max_retry) { $failure_msg .= sprintf( "\n%-20s%-30s\n", "Snap_Used", "Total_Snapshot_Spill" ); $failure_msg .= sprintf( "\n%-20s%-30s\n", $hash->{snap_used}, $hash->{total_spill}); $mismatched_value->{snap_used} = $hash->{snap_used}; $mismatched_value->{total_spill} = $hash->{total_spill}; } } if (defined($failure_msg)) { my $msg = "\n\nSpace Comparison failed for the volume $opts{volume}\n"; $msg .= $failure_msg if ($failure_msg); $Log->exit(); NACL::STask::Exceptions::SpaceComparisonFailure->throw( "\n\nSpace Comparison failed for the volume $opts{volume}\n".$failure_msg, mismatched_values => $mismatched_value ); } $Log->exit(); } ## end sub space_comparison =head2 zombie_all_files # Class method NACL::STask::Volume->zombie_all_files( command_interface => $command_interface, volume => $volume, vserver => $vserver, filter => { name => "$name", ## can be a regex. example to delete all the .sh files ## pass name as *.sh exclude => '1', ## To exclude the files from getting deleted. ## example to delete all files except .sh. Pass ## name => '"*.sh"' and set exclude to 1. type => 'f', ## 'f' is for file and 'd' is for directory. } %other_options ); (or) # Instance method $Vol_obj->zombie_all_files(); (Class and Instance method) This method deletes all the file in volume and make them zombie. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for instance method) Name of volume under test. =item C<< vserver => $vserver >> (Required for class method, Not Applicable for instance method) Name of volume under test. =item C<< filter => \%opts >> (Optional) This parameter accepts a hash-reference which contains the parameters required to find and delete the files/directories. eg : filter => { name => $name, exclude => "1", type => 'f', } if filter is not passed then all the files and directories will be deleted from the volume path. =back =back =over =item Exceptions =over =item C This type of exception is thrown when the Volume passed is not junctioned. =back =back =cut sub zombie_all_files { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => {filter => {type => HASHREF, optional => 1},}, ); my $api_options = delete $opts{filter}; my $path = $pkg_or_obj->systemshell_path(%opts); $api_options->{path} = $path; $api_options->{name} = '"*"' unless ($api_options->{name}); my $systemshell = $opts{command_interface}->apiset( category => 'Node', interface => 'CLI', set => 'Systemshell', ); #Get the list of the files based on filter. my $output = $systemshell->find(%{$api_options}); my $parsed_output = $output->get_parsed_output(); foreach my $file (@$parsed_output) { try { $systemshell->rm( 'paths' => $file, 'force' => 1, 'recursive' => 1, ); } catch NACL::APISet::Exceptions::CommandFailedException with { my $exception = $_[0]; if ($exception->text() =~ /.*\.snapshot:\s+(?:Operation\s+not\s+permitted|Read-only file system)/i) { ##Give warning $Log->warn($exception->text()); } else { $exception->throw(); } }; } $Log->exit(); return; } ## end sub zombie_all_files =head2 systemshell_path # Class method NACL::STask::Volume->systemshell_path( command_interface => $command_interface, volume => $volume, vserver => $vserver, %other_options ); (or) # Instance method $Vol_obj->systemshell_path(); (Class and Instance method) This method returns the Systemshell path of the Volume. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for instance method) Name of volume under test. =item C<< vserver => $vserver >> (Required for class method, Not Applicable for instance method) Name of volume under test. =back =back =over =item Exceptions =over =item C This type of exception is thrown when the Volume passed, is not junctioned. =back =back =cut sub systemshell_path { $Log->enter(); my ($pkg_or_obj,@args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args,); my %common_opts_with_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_with_ci, ); my $vol_cs = NACL::CS::Volume->fetch( %common_opts_with_ci, filter => { vserver => $opts{vserver}, volume => $opts{volume}, }, requested_fields => ['junction-path'] ); my $junction_path = $vol_cs->junction_path(); # if Volume is not junctioned then the junction path would be '-'. # In such case there will not be a volume file or volume dir in # systemshell under /clus. if ($junction_path =~ /-/) { $Log->exit(); NATE::Exceptions::Argument->throw( 'Volume passed is not junctioned'); } my $path = "/clus/$opts{vserver}" . '/' . $junction_path; $Log->exit(); return $path; } sub _get_cs_objects { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, allow_extra => 1 ); my (%common_opts,$node); $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts ); if ($common_opts{command_interface}->is_cmode() ) { my %node_call_opts = ( command_interface => $common_opts{command_interface}, volume => $opts{volume}, ); $node = $pkg_or_obj->get_local_node_name(%node_call_opts); } ## To fetch the CS objects for volume for show_space command my $vol_show_space_obj = NACL::C::Volume->show_space( %common_opts, filter => {volume => $opts{volume}}, ); my %hash; my $sum_of_fields; my $used_space = $vol_show_space_obj->{total_used}; # to convert it to bytes $used_space = NACL::C::UnitNormalization::convert_to_bytes($used_space); $hash{total_used} = $used_space; foreach my $key ('user_data', 'filesystem_metadata', 'snapshot_reserve', 'inodes') { $vol_show_space_obj->{$key} = NACL::C::UnitNormalization::convert_to_bytes($vol_show_space_obj->{$key}); $sum_of_fields += $vol_show_space_obj->{$key}; } $hash{sum_of_fields} = $sum_of_fields; my $apiset = $common_opts{command_interface}->get_7m_or_nodescope_apiset(); my $resp = $apiset->df( volume => 1, name => $opts{volume}, 'reserved-space' => 1, "scale-kilobytes" => 1, 'nodescope-node-name' => $node, )->get_parsed_output(); foreach my $line (@{$resp}) { foreach my $index ('total', 'used', 'avail', 'reserved') { $line->{$index} = NACL::C::UnitNormalization::convert_to_bytes( $line->{$index}); } } $hash{total_space} = $resp->[0]->{used} + $resp->[1]->{total}; if ($resp->[1]->{used} > $resp->[1]->{total}) { $hash{snap_space} = $resp->[1]->{used} - $resp->[1]->{total}; $hash{total_spill} = $vol_show_space_obj->{total_used} + $vol_show_space_obj->{snapshot_spill}; $hash{snap_used} = $resp->[1]->{used} + $resp->[0]->{used}; $hash{snapshot_spill} = $vol_show_space_obj->{snapshot_spill}; $hash{spill} = 1; } $Log->exit(); return \%hash; } # Consider the higher value for which the comparison is happening and # calculate the threshold value with the threshold_limit specified by the user # and compare this threshold value with the difference of two values, If theshold value # is less than the difference then this method returns 1 else returns 0 sub _space_range_check_failed { my @opts = @_; my $threshold_value = 0; my $target_value = 0; my $limit = $opts[2]; if((defined $opts[0]) && (defined $opts[1])) { if ($opts[0] < $opts[1]) { $target_value = $opts[1] - $opts[0]; $threshold_value = $opts[1] * ($limit/100); } else { $target_value = $opts[0] - $opts[1]; $threshold_value = $opts[0] * ($limit/100); } } ($threshold_value < $target_value) ? return 1 : return 0; } ## end sub _space_range_check_failed # #The spec for nacltask_attributes option in create method, this is used for #creating volumes with random attributes # sub _random_vol_attr_spec { return { type => HASHREF, default => { 'security-style' => [qw(unix mixed ntfs)], 'space-guarantee' => [qw(none volume )], 'nvfail' => [qw(on off)], } }; } ## end sub _random_vol_attr_spec =head2 wait_for_ondemand_to_finish NACL::STask::Volume->wait_for_ondemand_to_finish( command_interface => $ci, volume => $volume_A, vserver => $vserver_name, %other_opts ); $volume_A->wait_for_ondemand_to_finish() (Class or instance method) This method wait for any sfod jobs (move/copy) to finish where the volume 'A' is serving as source or destination of SFOD job (move/copy). Currently SFOD operation is considered is only VolumeFileCopy and VolumeFileMove. Applicable only for CMode. 7Mode is not supported as of now. =over =item Options =item C<< command_interface => $command_interface >> (Required for Class Method, Not Applicable for Instance Method) See L =item C<< volume => $volume >> (Required for Class Method, Not Applicable for Instance Method) Name of volume. =item C<< vserver=>$vserver_name >> (Required for Class Method, Not Applicable for Instance Method) Name of vserver containing volume. =back =cut sub wait_for_ondemand_to_finish { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, ); my %common_opts; $pkg_or_obj->_copy_common_component_params( source => \%opts, target => \%common_opts ); # Get the dsid of volume my $dsid = NACL::CS::Volume->fetch( command_interface => $opts{command_interface}, %common_opts, requested_fields => ['dsid'], filter => { volume => $opts{volume}, vserver => $opts{vserver}, }, )->dsid(); my @volume_file_obj; # Get all the file Move/Copy object for volume # whose source-dsid or destination-dsid is dsid of the volume foreach ('source-dsid', 'destination-dsid') { my %args = ( command_interface => $opts{command_interface}, filter => { $_ => $dsid }, %common_opts, 'allow_empty' => 1 ); # Get the Volume file move/copy operation push ( @volume_file_obj, ( NACL::STask::VolumeFileMove->find(%args), NACL::STask::VolumeFileCopy->find(%args) ) ); } # Wait till all the SFOD opeartion is finished. map { $_->wait_for_finish(%common_opts) } @volume_file_obj; $Log->exit(); } ## end sub wait_for_ondemand_to_finish =head2 clear_error NACL::STask::Volume->clear_error( command_interface => $ci, volume => $volume_name, vserver => $vserver_name, # Required for CMode inaccessible => $boolean, # default '1' nacltask_wait => $boolean, # default '1' %other_option ); or $vol_obj->clear_error( %other_option ); (Class or Instance Method) clears iron marked inaccessible block ranges from AFS of the volume. Supports 7Mode/Nodescope CLI. Invokes "vol_clear_error" for 7Mode/Nodescope CLI. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for Instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for Instance method) File path =item C<< vserver => $vserver_name >> (Required for class method in CMode, Not Applicable for Instance method or for 7Mode) The vserver on which the file is present. =item C<< nacltask_wait => 0|1 >> (Optional) If 1 (the default), wait for volume clear error should complete. This is achieved by the wafl scan status command, till the status become "VBN to inode translation". =item Other options All of the other options accepted by L. =back =back =over =item Exceptions =over =item C In general, Component methods propagate the exceptions thrown from underlying API layer and the details of those exceptions are described in the L of user guide. =item C This type of exception is thrown when an attempt is made to clear_error on the volume that doesn't exists. =back =back =cut sub clear_error { $Log->enter(); my ($pkg_or_obj, @args) = @_; $pkg_or_obj->_frontend_log_debug_opts(@args); my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { nacltask_wait => {type => BOOLEAN, default => 1}, $pkg_or_obj->_nodescope_options_validate_spec(), handle_name_ordinal => {type => SCALAR, default => 1}, }, allow_extra => 1 ); my $wait = delete $opts{nacltask_wait}; # Call _get_node_and_handle_ordinal, so that name ordinal # and nodescope name will update in %opts hash, # inorder to avoid two repeated RPC call. $pkg_or_obj->_get_node_and_handle_ordinal(\%opts); my $pkg_name = $pkg_or_obj->get_package_name(); # Make a class call, since volume attribute is # updated in %opts hash. Object call will throw validation error $pkg_name->SUPER::clear_error(%opts); if ($wait) { $pkg_name->wait_for_clear_error_scanner(%opts); } $Log->exit(); } =head2 wait_for_clear_error_scanner NACL::STask::Volume->wait_for_clear_error_scanner( command_interface => $ci, volume => $volume_name, vserver => $vserver_name, # Required for CMode inaccessible => $boolean, # default '1' nacltask_wait => $boolean, # default '1' %other_option ); or $vol_obj->wait_for_clear_error_scanner( %other_option ); (Class or Instance Method) Track the wafl scanners on volume. Supports 7Mode/Nodescope CLI. Invokes "wafl_scan_status" for 7Mode/Nodescope CLI. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for Instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for Instance method) File path =item C<< vserver => $vserver_name >> (Required for class method in CMode, Not Applicable for Instance method or for 7Mode) The vserver on which the file is present. =item C<< nodescope-node-name => $node_name >> (Optional) When run in nodescope, the component method defaults to running on the "local" node. This argument can be used to run the method on node other than "local". Applicable for 7Mode CLI/Nodescope . =item C<< determine_appropriate_nodescope_node => 0|1 >> (Optional, defaults to 0) When set to 1, the method first determines which node the volume is present and makes the call in the context of that node. Note that when C overrides this argument, meaning that if both C is provided and C is set to 1 then the node name provided in C is used. This argument defaults to 0. Applicable for 7Mode CLI/Nodescope. =item C<< handle_name_ordinal => 0 | 1 >> (Optional, defaults to 1) (This is applicable only for CMode) Please refer the concept of handle_name_ordinal in L. =back =back =over =item Exceptions =over =item C In general, Component methods propagate the exceptions thrown from underlying API layer and the details of those exceptions are described in the L of user guide. =item C This type of exception is thrown when clear_error scanners does not completed within the method-timeout. =back =back =cut sub wait_for_clear_error_scanner { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { $pkg_or_obj->_nodescope_options_validate_spec(), handle_name_ordinal => {type => SCALAR, default => 1}, }, allow_extra => 1 ); $pkg_or_obj->_get_node_and_handle_ordinal(\%opts); my %api_arggs; $api_arggs{volume} = $opts{volume}; # _get_node_and_handle_ordinal method returns the path # like '/vol/vol_name', however wafl_scan_status won't work # with if the path '/vol/vol_name' it will only work with 'vol_name' $api_arggs{volume} =~ s/^\/vol\///; $api_arggs{'nodescope-node-name'} = $opts{'nodescope-node-name'}; my $apiset = $opts{command_interface}->get_7m_or_nodescope_apiset(); my $method_time_out = $opts{'method-timeout'} || 300; my $time = time() + $method_time_out; my $completed = 0; while($time > time()) { my $res = $apiset->wafl_scan_status(%api_arggs); $completed = 1; if ($res->get_raw_output() =~ /No WAFL scans in progress/) { last; } else { my $parsed_output = $res->get_parsed_output(); foreach my $raw (@{$parsed_output->[0]->{info}}) { if($raw->{type_of_scan} =~ /VBN to inode translation/i) { $completed = 0; last; } } } last if ($completed); Tharn::snooze 15; } if(!$completed) { NACL::Exceptions::Timeout->throw("wafl scan is not able to complete within ". "$method_time_out seconds."); } $Log->exit(); } =head2 trigger_nvfailed_state NACL::STask::Volume->trigger_nvfailed_state( command_interface => $ci, volume => $volume_name, vserver => $vserver_name, # Required for CMode %other_option ); or $vol_obj->trigger_nvfailed_state( %other_option ); (Class or Instance Method) Puts the volume into in-nvfailed state. Steps to bring the volume into in-nvfailed state: step1: Turn on the NVFAIL state to "ON" for the volume to be nvfailed. step2: Invoke the wafl_sync on volume to wait for a CP. step3: nv_trash the node of the volume to be nv-failed. This will panic and reboot the local node of the volume. step4: Wait for the node to come back up. step5: Verify the volume is in the in-nvfailed state. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for Instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for Instance method) File path =item C<< vserver => $vserver_name >> (Required for class method in CMode, Not Applicable for Instance method or for 7Mode) The vserver on which the file is present. =back =back =over =item Exceptions =over =item C This type of exception is thrown when verification of the volume for in-nvfailed-state has failed i.e. if in-nvfailed-state = false. =back =back =cut sub trigger_nvfailed_state { $Log->enter(); my ( $pkg_or_obj, @args ) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec =>{ nacltask_verify => { type => BOOLEAN, default => 1 }, "method-timeout" => {type => SCALAR, default => 1800}, } ); my $partner_node ; my $verify = delete $opts{nacltask_verify}; my $timeout = delete $opts{'method-timeout'}; my $node = $pkg_or_obj->get_local_node_name(%opts); my $node_obj = NACL::STask::Node->new(node => $node); my $ci = $opts{command_interface}; ## to bring back the node from out of quorum state ## the partner node is needed try{ $partner_node = NACL::STask::Node->get_partner_obj( command_interface => $node_obj, ); }catch NACL::Exceptions::HighAvailabilityError with{ $Log->warn('The HA partner of the node '.$node.' is not available, It may be possible that its a single node cluster or the node doesnt have the HA partner'); }; # Turn on the NVFAIL state to "ON" for the volume to be nvfailed. $pkg_or_obj->modify( %opts, nvfail => 'on', nacltask_verify => 1 ); # Invoke the wafl_sync on volume to wait for a CP. $ci->apiset( interface => 'ZAPI' )->wafl_sync( volumes => [ $opts{volume} ], 'vserver-override-name' => $opts{vserver}, ); my %common_comp_opts; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_comp_opts ); my $apiset = $ci->get_7m_or_nodescope_apiset(); #nv_trash the node of the nvfail volume. $apiset->execute_command( command => "nv_trash toc all 42", 'nodescope-node-name' => $node ); my $transit_obj = NACL::Transit->new( name => $node ); try { # Check for PANIC state $transit_obj->wait_for_state(wait_for => 'PANIC'); } catch NACL::Transit::Exceptions::Timeout with { # Panic is not occured NACL::STask::Node->panic( node => $node, nacltask_reboot => 0, %common_comp_opts ); }; $transit_obj->change_state( to => 'CLI' ); # Refresh the interface after the reboot $ci->refresh_command_interface(); my $quorum_opts = { 'method-timeout' => $timeout, 'partner' => $partner_node }; try{ $pkg_or_obj->verify_state( %opts, 'in-nvfailed-state' => 'true', %common_comp_opts ); }finally{ $Log->debug("Recovering the node back from out of quorum state"); ## partner node is needed to recover the node $node_obj->recover_from_quorum_errors(%{$quorum_opts}) if($partner_node); $ci->refresh_command_interface(); $Log->exit(); }; } =head2 force_cp NACL::STask::Volume->force_cp( command_interface => $ci, volume => $volume_name, vserver => $vserver_name, # Required for CMode nacltask_wait => $boolean, # default '1' 'method-timeout' => $timeout, #default to 600 sec, %other_option ); or $vol_obj->force_cp( %other_option ); (Class or Instance Method) Triggers a forced wafl sync on volume. =over =item Options =over =item C<< command_interface => $command_interface >> (Required for class method, Not Applicable for Instance method) See L =item C<< volume => $volume_name >> (Required for class method, Not Applicable for Instance method) File path =item C<< vserver => $vserver_name >> (Required for class method in CMode, Not Applicable for Instance method or for 7Mode) The vserver on which the file is present. =item C<< nacltask_wait => 0|1 >> (Optional) If 1 (the default), wait for CP to complete on a Volume =item C<< "method-timeout" => $timeout >> (Optional) How long in seconds to wait for CP to complete on a Volume. Default is 600 sec. =item Other options All of the other options accepted by L. =back =back =over =item Exceptions =over =item C In general, Component methods propagate the exceptions thrown from underlying API layer and the details of those exceptions are described in the L of user guide. =item C This type of exception is thrown when an attempt is made to force_cp on the volume that doesn't exists. =back =back =cut sub force_cp { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { nacltask_wait => {type => BOOLEAN, default => 1}, 'method-timeout' => {type => SCALAR, default => 600}, }, allow_extra => 1, ); my $wait = delete $opts{nacltask_wait}; my $timeout = delete $opts{'method-timeout'}; my $handle = $pkg_or_obj->SUPER::force_cp(%opts); if ($wait) { my $status; my $end_time = time() + $timeout; my $zapi = $opts{command_interface}->apiset( category => 'Node', interface => 'ZAPI', set => 'CMode', ); do { if (time() > $end_time) { NACL::Exceptions::Timeout->throw( 'Requested a CP, but it did not complete within 600 seconds' ); } my $resp_obj = $zapi->wafl_get_sync_status( 'sync-handle' => $handle, 'vserver-override-name' => $opts{vserver}, ); my $parsed = $resp_obj->get_parsed_output(); #$parsed->[0]->{status} is a REF of an ARRAY of two status values: #the first one is the status of the ZAPI command and #the second one is the status of the wafl sync. #We should use the second one here. $status = $parsed->[0]->{status}->[1]; sleep(1); # We will accept success only for making the CP. If it failed, then # an exception should be thrown. if( $status =~ /failed/i ) { NACL::APISet::Exceptions::ResponseException->throw( text => "The WAFL SYNC failed", response_object => $resp_obj, ); } } while ($status !~ /succeeded/i); } $Log->exit(); } =head2 get_constituents # Works on an Infinite Volume my @constituent_objs = $infinivol_obj->get_constituents(); # Works on a FlexGroup my @constituent_objs = $flexgrp_obj->get_constituents(); # Scalar context returns only the first matching constituent my $constituent = $iv_or_fg_obj->get_constituents(); # Can provide other options accepted by find. This example shows how # to get the "root" constituent my $root = $fg_obj->get_constituents( filter => {'constituent-role' => 'root'}); # All of the above work as a package call as well my @constituents = NACL::STask::Volume->get_constituents( command_interface => $ci, volume => $volume, vserver => $vserver, ); Returns C objects for constituents of the provided volume. The volume provided B to be either an Infinite Volume (IV) or a FlexGroup (FG). Attempting to invoke this method on any other type of volume (such as a FlexVol) will result in a C exception being thrown. This method automatically determines if the volume provided is an IV or an FG and uses the appropriate logic to determine the constituent volumes. If it is known that a volume is an FG, then L can be directly invoked. Similarly, if a known is known to be an IV, then L can be invoked directly. This method provides all of the features typically provided by C methods, which include, but are not limited to: =over =item Scalar/List context Ability to use the method in scalar (returns the first matching object) or list context (returns all objects) =item Filtering The C option can be used to only return objects that match the specified filter criteria. =item Other find options This method accepts all of the other options accepted by C, such as C, C, C and so on. =back =over =item Options All of the options accepted by C are accepted by this method. =back =cut sub get_constituents { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, # Allow through the options accepted by the specific get_* method allow_extra => 1, ); # If invoked with an object, then the object might already know if it's an # IV or FG my $is_infinivol = $pkg_or_obj->_is_infinivol(); my $is_flexgroup = $pkg_or_obj->_is_flexgroup(); if (!$is_infinivol && !$is_flexgroup) { # Need to figure out if an IV or FG my %common_opts_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_ci ); my $vol_cs = NACL::CS::Volume->fetch( %common_opts_ci, filter => {volume => $opts{volume}, vserver => $opts{vserver}}, requested_fields => [qw(volume-style is-flexgroup)] ); if ($vol_cs->volume_style() eq 'infinitevol') { $is_infinivol = 1; } elsif ($vol_cs->is_flexgroup() eq 'true') { $is_flexgroup = 1; } else { $Log->exit(); NATE::Exceptions::Argument->throw('The volume provided (volume=' . "$opts{volume}, vserver=$opts{vserver} is neither an " . 'InfiniteVol nor a FlexGroup' ); } } my @constituents; if ($is_infinivol) { @constituents = $pkg_or_obj->get_infinivol_constituents(%opts); } else { @constituents = $pkg_or_obj->get_flexgroup_constituents(%opts); } $Log->exit(); return wantarray ? @constituents : $constituents[0]; } =head2 get_flexgroup_constituents Returns the constituents of the provided FlexGroup. A detailed description of the functionality, options accepted, along with examples are provided in the documentation of the L method. =cut sub get_flexgroup_constituents { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => {filter => {type => HASHREF, default => {}}}, # Allow through the options accepted by "find" allow_extra => 1, ); my $filter = delete $opts{filter}; my %common_opts_ci; $pkg_or_obj->_copy_common_component_params_with_ci( source => \%opts, target => \%common_opts_ci ); # Constituents of an FG have the same flexgroup-msid as the FG my $fg_msid = NACL::CS::Volume->fetch( %common_opts_ci, filter => { volume => $opts{volume}, vserver => $opts{vserver}, }, requested_fields => [qw(flexgroup-msid)] )->flexgroup_msid(); my %find_opts; $pkg_or_obj->_hash_copy( source => \%opts, target => \%find_opts, copy => [keys %{$pkg_or_obj->_find_validate_spec}] ); my @constituents = NACL::STask::Volume->find( %find_opts, filter => { 'flexgroup-msid' => $fg_msid, 'is-constituent' => 'true', %$filter, }, ); # We know that constituents are neither IVs nor FGs, so set these on the # objects so that subsequent calls don't need to run extra commands to figure # this out. map {$_->_is_infinivol(0); $_->_is_flexgroup(0);} @constituents; $Log->exit(); return wantarray ? @constituents : $constituents[0]; } =head2 get_infinivol_constituents Returns the constituents of the provided Infinite Volume. A detailed description of the functionality, options accepted, along with examples are provided in the documentation of the L method. =cut sub get_infinivol_constituents { $Log->enter(); my ($pkg_or_obj, @args) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => {filter => {type => HASHREF, default => {}}}, # Allow through the options accepted by "find" allow_extra => 1, ); my $filter = delete $opts{filter}; my %find_opts; $pkg_or_obj->_hash_copy( source => \%opts, target => \%find_opts, copy => [keys %{$pkg_or_obj->_find_validate_spec}] ); my @constituents = NACL::STask::Volume->find( %find_opts, filter => { vserver => $opts{vserver}, 'is-constituent' => 'true', %$filter, }, ); # We know that constituents are neither IVs nor FGs, so set these on the # objects so that subsequent calls don't need to run extra commands to figure # this out. map {$_->_is_infinivol(0); $_->_is_flexgroup(0);} @constituents; $Log->exit(); return wantarray ? @constituents : $constituents[0]; } 1;