# # Copyright (c) 2001-2010 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary common routines for NACL::STask::Aggregate and NACL::STask::Volume ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here package NACL::STask::_AggregateVolume; use strict; use warnings; use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use NACL::Cleanup; use NACL::Exceptions::UnexpectedAVState qw(:try); use NACL::APISet::Exceptions::TimeoutException (); use NACL::APISet::Exceptions::ResponseException (); use NATE::Exceptions::Argument; use NACL::APISet::Exceptions::CommandFailedException qw(:try); use NACL::C::DebugVreport; use NATE::BaseException; use Params::Validate qw(validate SCALAR validate_with SCALARREF HASHREF OBJECT UNDEF validate_pos); use NACL::ComponentUtils qw(Dumper); use NACL::Exceptions::Timeout; use NACL::GeneralUtils qw(nacl_method_retry); BEGIN { use Exporter qw(import); our %EXPORT_TAGS = ( all => [ qw(AGGREGATE_TIMEOUT _wait_for_completion taskverify_modify taskverify_purge POLL_DELTA online offline restrict shared_or_recyclable_common_create_or_undo _find_free_resource _validate_filter recyclable_create shared_create _extract_job recyclable_undo shared_undo last_job last_job_isset last_job_reset vreport_fix _job_validate_spec _change_state _valid_intermediate_states _register_for_unmount _register_for_rename _rename ) ] ); our @EXPORT_OK = @{$EXPORT_TAGS{all}}; } use constant AGGREGATE_TIMEOUT => 7200; use constant POLL_DELTA => 10; =head1 NAME NACL::STask::_AggregateVolume =head1 DESCRIPTION Common code for Aggregate and Volume stask implementation. =cut # Validate job options sub _job_validate_spec { return ( job => {type => OBJECT | UNDEF, optional => 1}, job_component => {type => OBJECT | UNDEF, optional => 1}, ); } # Extract job_component, delete job sub _extract_job { $Log->enter() if $may_enter; my ($pkg_or_obj, %opts) = @_; $opts{job_component} ||= $opts{job} || $pkg_or_obj->job_component(); delete $opts{job}; $Log->exit() if $may_exit; return %opts; } sub _valid_intermediate_states { return ['creating', 'mounting', 'mixed']; } # The verification routine for "modify". It simply re-uses the generic create # and modify verification sub taskverify_modify { $Log->enter() if $may_enter; my $pkg_or_obj = shift; $pkg_or_obj->verify_state(@_); $Log->exit() if $may_exit; } ## end sub taskverify_modify # Verify that the aggregate was purged. This is done by checking that the # aggregate/volume does not exist sub taskverify_purge { $Log->enter() if $may_enter; my $pkg_or_obj = shift; $pkg_or_obj->_generic_purge_verify(@_); $Log->exit() if $may_exit; } ## end sub taskverify_purge sub shared_create { $Log->enter() if $may_enter; my $pkg = shift; my %opts = $pkg->_common_validate_with( params => \@_, additional_spec => { nacltask_cleanup_manager => { type => OBJECT, isa => 'NACL::Cleanup', optional => 1 }, }, allow_extra => 1, ); my $task_obj = $pkg->shared_or_recyclable_common_create_or_undo( %opts, resource_type => "shared", action => "create" ); $Log->exit() if $may_exit; return $task_obj; } ## end sub shared_create sub recyclable_create { $Log->enter() if $may_enter; my $pkg = shift; my %opts = $pkg->_common_validate_with( params => \@_, additional_spec => { nacltask_cleanup_manager => { type => OBJECT, isa => 'NACL::Cleanup', optional => 1 }, }, allow_extra => 1, ); my $task_obj = $pkg->shared_or_recyclable_common_create_or_undo( %opts, resource_type => "recyclable", action => "create" ); $Log->exit() if $may_exit; return $task_obj; } ## end sub recyclable_create sub shared_undo { $Log->enter() if $may_enter; my $pkg = shift; my %opts = $pkg->_common_validate_with( params => \@_, allow_extra => 1, ); my $task_obj = $pkg->shared_or_recyclable_common_create_or_undo( %opts, resource_type => "shared", action => "undo" ); $Log->exit() if $may_exit; return $task_obj; } ## end sub shared_undo sub recyclable_undo { $Log->enter() if $may_enter; my $pkg = shift; my %opts = $pkg->_common_validate_with( params => \@_, allow_extra => 1, ); my $task_obj = $pkg->shared_or_recyclable_common_create_or_undo( %opts, resource_type => "recyclable", action => "undo" ); $Log->exit() if $may_exit; return $task_obj; } ## end sub recyclable_undo sub shared_or_recyclable_common_create_or_undo { $Log->enter() if $may_enter; my $pkg = shift; my %opts = @_; my %common_component_opts; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_component_opts, ); my $resource = ($pkg =~ /Aggregate/i) ? "aggregate" : "volume"; my $newname = $opts{$resource}; my $type = delete $opts{resource_type}; my $action = $opts{action}; my $nacltask_cleanup_manager = delete $opts{nacltask_cleanup_manager}; # Find the free shared/recyclable aggregate/volume which matches the specified criteria. my $free_resource_obj = $pkg->_find_free_resource( %opts, type => $type, resource => $resource ); if (ref $free_resource_obj) { if ($action eq "create") { $newname =~ s/^_//; } elsif ($action eq "undo") { $newname = '_' . $free_resource_obj->$resource; } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("Invalid action - $action"); } if ($type eq "shared") { $newname =~ s/^(_?..)/uc($1)/e; } elsif ($type eq "recyclable") { $newname =~ s/^(_?..)/lc($1)/e; } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "resource type - $type unsupported"); } } else { $Log->exit() if $may_exit; NACL::Exceptions::NoElementsFound->throw( "No $resource found matching the criteria"); } $free_resource_obj->rename(newname => $newname); # use global cleanup manager if not specified my $cleanup_obj = $nacltask_cleanup_manager || NACL::Cleanup->new(); if ( $action eq 'create' ) { my $to_cleanup = $type . "_undo"; # build the Cleanup method name $free_resource_obj->{cleanup_state} = 1; my %cleanup_opts; my $nacltask_to_cleanup; $free_resource_obj->_copy_common_opts_for_cleanup( 'source' => {'nacltask_cleanup_manager' => $cleanup_obj}, 'target' => \%cleanup_opts, 'nacltask_to_cleanup' => \$nacltask_to_cleanup, 'to_cleanup' => $to_cleanup ); $free_resource_obj->_register_for_cleanup(%cleanup_opts); } elsif ( $action eq 'undo' ) { #$free_resource_obj->{to_cleanup} = undef; $free_resource_obj->{cleanup_state} = 0; # TBD update the entry in RL #$cleanup_obj->update(objects => [$free_resource_obj]); # update entry in RL } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("Internal Error: Invalid action - ". "'$action'". "\nEmail dl-nacl-dev or create ". "BURT(type=NACL,subtype=nacl_core)"); } $Log->exit() if $may_exit; return $free_resource_obj; } ## end sub shared_or_recyclable_common_create_or_undo sub _find_free_resource { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg->_common_validate_with( params => \@_, ignore_primary_keys => 1, allow_extra => 1, ); my $resource_type = delete $opts{type}; my $resource = delete $opts{resource}; my $action = delete $opts{action}; my $filter = {}; my $prefix; if ($action eq "create") { $prefix = ($resource =~ /aggregate/i) ? q#_ag*# : q#_vl*#; delete $opts{$resource}; } elsif ($action eq "undo") { $prefix = ($resource =~ /aggregate/i) ? q#ag_*# : q#vl_*#; # ensure that the resource name adheres to NOTE naming standards. if ($resource_type eq "shared") { $opts{$resource} =~ s/^(_?..)/uc($1)/e; } elsif ($resource_type eq "recyclable") { $opts{$resource} =~ s/^(_?..)/lc($1)/e; } else { NATE::Exceptions::Argument->throw( "resource type - $resource_type unsupported"); } # copy the primary keys into the filter hash $pkg->_hash_copy( source => \%opts, target => $filter, copy => [$pkg->get_primary_keys()] ); } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("Action - $action is unsupported"); } # Only if it is for create, change the resource name in the filter to # have only the prefix if ($action eq "create") { if ($resource_type eq "recyclable") { $filter->{$resource} = $prefix; } elsif ($resource_type eq "shared") { $filter->{$resource} = uc $prefix; } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "resource type - $resource_type unsupported"); } } my %common_opts; $pkg->_hash_move( source => \%opts, target => \%common_opts, move => [keys %{$pkg->_common_validate_spec()}] ); my $CS_pkg = $pkg->get_CS_package_name(); my @cs_objs = $CS_pkg->fetch(%common_opts, 'filter' => $filter); # return 0 when no resource found matching the criteria. # exception will be thrown in caller if (!scalar @cs_objs) { $Log->exit() if $may_exit; return 0; } foreach my $cs_obj (@cs_objs) { my $match; while (my ($filter_attrib, $criteria) = each %opts) { my $isset = "${filter_attrib}_isset"; if ($cs_obj->$isset()) { my %args_to_validate_filter = ref $criteria ? %{$criteria} : (scalar_val => $criteria); my $cs_attrib_val = $cs_obj->$filter_attrib(); $match = $pkg->_validate_filter( value => $cs_attrib_val, %args_to_validate_filter ); if (!$match) { # return 0 when no resource found matching the criteria. # exception will be thrown in caller $Log->exit() if $may_exit; return 0; } } } $Log->exit() if $may_exit; return $cs_obj->get_task_instance(); } } ## end sub _find_free_resource sub _validate_filter { $Log->enter() if $may_enter; my $pkg = shift; my %opts = @_; my $value = delete $opts{value}; if (ref $value && (ref $value ne "ARRAY")) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Value is of type " . ref $value . " is not supported"); } foreach my $criteria (keys %opts) { if ($criteria =~ /^(or|not)$/) { my $regex; if ($criteria eq 'or') { my $matched; $regex = '(' . join("|", map {"$_"} @{$opts{$criteria}}) . ')'; if (ref $value eq "ARRAY") { foreach my $arr_elem (@{$value}) { my $temp = $arr_elem =~ /$regex/; $matched ||= ($arr_elem =~ /$regex/); } } else { $matched = ($regex =~ /$value/); } $Log->exit() if $may_exit; return 0 if (!$matched); } elsif ($criteria eq 'not') { my $matched = 1; my $condition; if (ref $value eq "ARRAY") { foreach my $arr_elem (@{$value}) { $condition = join " && ", map { "(\"$arr_elem\" ne \"$_\")"; } @{$opts{$criteria}}; $matched &&= eval "$condition"; } } else { $condition = join " && ", map { "(\"$value\" ne \"$_\")"; } @{$opts{$criteria}}; $matched = eval "$condition"; } $Log->exit() if $may_exit; return 0 if (!$matched); } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "$criteria is is not supported."); } } elsif ($criteria =~ /^[<>=]+$/) { my $filter_val = $opts{$criteria}; if ($filter_val =~ /b|[kmgt]b?/i) { $filter_val = NACL::C::UnitNormalization::convert_to_bytes( $filter_val); } $Log->exit() if $may_exit; return 0 if (!(eval "$filter_val $criteria $value")); } else { $Log->exit() if $may_exit; return 0 if ($value ne $opts{$criteria}); } } $Log->exit() if $may_exit; return 1; } ## end sub _validate_filter # The "job_component" attribute was earlier called "last_job". These methods # are to provide backwards compatibility sub last_job { my $pkg_or_obj = shift; return $pkg_or_obj->job_component(@_); } sub last_job_isset { my $pkg_or_obj = shift; return $pkg_or_obj->job_component_isset(); } sub last_job_reset { my $pkg_or_obj = shift; return $pkg_or_obj->job_component_reset(); } # Helper common method for NACL::STask::Volume and NACL::STask::Aggregate sub vreport_fix { $Log->enter() if $may_enter; my $pkg_or_obj = shift; my $pkg = ref($pkg_or_obj) || $pkg_or_obj; my %opts = $pkg_or_obj->_common_validate_with(params => \@_); my $mode = $opts{command_interface}->mode(); if ($mode ne "CMode") { $Log->exit() if $may_exit; NATE::BaseException->throw("This method is not valid on " . $mode); } my %common_component_opts; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_component_opts, ); $pkg =~ /::(Aggregate|Volume)$/; my $type = lc($1); my $args = {}; $pkg->_hash_copy( source => \%opts, target => $args, map => { "volume" => "object", "aggregate" => "object", } ); if ($type eq "volume") { if (!$opts{'vserver'}) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "\"vserver\" is mandatory argument for vreport fix for volume" ); } $args->{object} = $opts{'vserver'} . ':' . $args->{object}; } $args->{type} = $type; $Log->exit() if $may_exit; NACL::C::DebugVreport->fix(%{$args}, %common_component_opts); } ## end sub vreport_fix sub restrict { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; $pkg_or_obj->_change_state(@args, '_method_name' => 'restrict'); $Log->exit() if $may_exit; } sub online { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; $pkg_or_obj->_change_state(@args, '_method_name' => 'online'); $Log->exit() if $may_exit; } sub offline { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; $pkg_or_obj->_change_state(@args, '_method_name' => 'offline'); $Log->exit() if $may_exit; } sub _change_state { $Log->enter() if $may_enter; my ( $pkg_or_obj, @args ) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { $pkg_or_obj->_cleanup_validate_spec(), _method_name => { type => SCALAR }, # offline, online, restrict # Needed for the async volume ZAPI calls nacltask_wait => { type => SCALAR, default => 1 }, 'method-timeout' => { type => SCALAR, default => 300 }, }, allow_extra => 1 ); my $method_name = delete $opts{_method_name}; my ( %opts_for_register, $nacltask_to_cleanup ); $pkg_or_obj->_copy_common_opts_for_cleanup( 'source' => \%opts, 'target' => \%opts_for_register, 'nacltask_to_cleanup' => \$nacltask_to_cleanup, 'to_cleanup' => 'modify' ); my %nacltask_options; $pkg_or_obj->_move_nacltask_options( source => \%opts, target => \%nacltask_options ); if ($nacltask_to_cleanup) { # get the current state of the options so it can be used during cleanup my ( %orig_opts, %new_opts, %common_component_params_with_ci ); %new_opts = %opts; if ( $new_opts{force} ) { $new_opts{state} = 'force-' . $method_name; # only for offline delete $new_opts{force}; } else { $new_opts{state} = '$method_name'; } # remove common options and primary keys # what remains are the args to the CLI $pkg_or_obj->_move_common_component_params_with_ci( source => \%new_opts, target => {}, ); my %primary_keys; $pkg_or_obj->_move_primary_keys( source => \%new_opts, target => \%primary_keys, ); %orig_opts = %new_opts; my @requested_fields = ( keys %orig_opts ); my $cs_pkg = $pkg_or_obj->get_CS_package_name(); my $cs_obj = $cs_pkg->fetch( command_interface => $opts{command_interface}, filter => \%primary_keys, requested_fields => \@requested_fields ); foreach my $field ( keys %orig_opts ) { $orig_opts{$field} = $cs_obj->$field; } $Log->debug( sub { "_change_state: original options are " . Dumper(\%orig_opts) } ); $opts_for_register{'orig_opts'} = \%orig_opts; $opts_for_register{'new_opts'} = \%new_opts; } $Log->debug( sub { "_change_state: options to '$method_name' are " . Dumper(\%opts) } ); my $dummy; $opts{job_component} ||= \$dummy; my $c_super_method = "C_SUPER::$method_name"; $pkg_or_obj->$c_super_method(%opts); # Register this resource with the Cleanup Manager for cleanup # the cleanup method for offline, online and restrict is 'modify' if ($nacltask_to_cleanup) { $pkg_or_obj->_register_for_cleanup(%opts_for_register); } if ($nacltask_options{nacltask_wait}) { $pkg_or_obj->_wait_for_async_zapi(%opts); } $Log->exit() if $may_exit; } sub _register_for_unmount { $Log->enter() if $may_enter; my ( $self, @args ) = @_; my %opts = validate_with( params => \@args, spec => {$self->_register_for_cleanup_validate_spec()}, allow_extra => 1, ); my $existing_to_cleanup = $opts{'cleanup_method_registerd'}; my $new_to_cleanup = $opts{'cleanup_method_to_update'}; my $cleanup_manager = $opts{'cleanup_manager'}; if ( $existing_to_cleanup ne $new_to_cleanup ) { # add unmount to the list of cleanups to be done # add to the beginning since modify etc cannot be done # till unmounted my $existing_obj = $opts{'existing_obj'}; $existing_obj->[0]{to_cleanup} = 'unmount+' . $existing_to_cleanup; my $updated_obj = $cleanup_manager->update( objects => $existing_obj ); $Log->debug( "updating the existing realm object: \n" . Dumper($existing_obj) . "..." ); $Log->debug( "updated object is: \n" . Dumper($updated_obj) ); } $Log->exit() if $may_exit; } sub _register_for_rename { $Log->enter() if $may_enter; my ( $self, @args ) = @_; $self->_register_for_modify_or_rename(@args); $Log->exit() if $may_exit; } # This is a common method used to register the rename operatoin in cleanup for both aggregate and volume sub _rename { $Log->enter() if $may_enter; my ( $pkg_or_obj, @args ) = @_; my %opts = $pkg_or_obj->_common_validate_with( params => \@args, additional_spec => { $pkg_or_obj->_cleanup_validate_spec(), element => { type => SCALAR }, 'method-timeout' => { type => SCALAR, default => 300 }, }, allow_extra => 1 ); my ( %nacltask_options, $nacltask_to_cleanup, %opts_for_cleanup ); $pkg_or_obj->_copy_common_opts_for_cleanup( 'source' => \%opts, 'target' => \%opts_for_cleanup, 'nacltask_to_cleanup' => \$nacltask_to_cleanup, 'to_cleanup' => 'rename' ); $pkg_or_obj->_move_nacltask_options( source => \%opts, target => \%nacltask_options ); my $element = delete $opts{'element'}; $pkg_or_obj->C_SUPER::rename(%opts); if ($nacltask_to_cleanup) { $opts_for_cleanup{'new_opts'} = { 'newname' => $opts{'newname'} }; $opts_for_cleanup{'orig_opts'} = { 'newname' => $opts{$element} }; $opts_for_cleanup{$element} = $opts{'newname'}; $pkg_or_obj->_register_for_cleanup(%opts_for_cleanup); } $Log->exit() if $may_exit; } 1;