# Copyright (c) 2014 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary Volume Lun Path Management MTask module ## @author garveyj@netapp.com, dl-ptefit-automation-core@netapp.com ## @status shared ## @pod here =head1 NAME NACL::MTask::VolumeLunMapping =head1 DESCRIPTION C provides a number of well-defined but potentially complex or multi-step methods related to Lun Mappings in ONTAP. This MTask only supports Fullsteam and above. Lun mapping capability was not present in earlier releases. It is intended to be executed with a volume move operation between the add/remove calls. This MTask does not create volumes or luns; it only modifies the list of reporting nodes for existing luns on the volumes the user specifies, using the MTask's 'volumes' parameter. This MTask only supports instance calls. =head1 METHODS =head2 create # Example for VolumeLunMapping Create my $volume_lun_mapping_obj = NACL::MTask::VolumeLunMapping->create( command_interface => $command_interface, volumes => $volumes, build_lun_metadata => $build_flag, ); (Class method) This method will simply return a VolumeLunMapping object to allow method calls for defined methods. =over =item C<< command_interface => $command_interface >> (Required) This is the component object that represents the host to which the commands are sent. =item C<< volumes => $volumes >> (Required) An array of the volume or volumes for which the the reporting nodes should be added. The user defines the selection of volumes by constructing an array input with a specific format: [ { 'vol_name' => $vol_name, 'vserver_name' => $vserver_name, 'source_nodes' => $source_nodes, 'destination_nodes' => $destination_nodes, 'destination_aggr' => $destination_aggr, 'source_aggr' => $source_aggr, 'local-nodes' => $local_nodes, 'destination-volume' => $destination_volume, 'all' => $bool, 'add-nodes' => $add_nodes, 'remove-remote-nodes' => $bool, 'remove-nodes' => $remove_nodes, }, {... Details for vol2 ...}, {... Details for vol3 ...}, {... Details for vol4 ...}, {...}, ] B For each volume that you wish to modify the child luns reporting nodes for, a volume hash must exist. These volume hashes include the information about the volume and options for add/remove_reporting_nodes commands that that will be used for each lun on that volume. These volume hash are put in a list, and that list is sent into the VolumeLunMapping MTask as the 'volumes' parameter. The details of the valid paramters for each volume hash are: =over =item C<< vol_name => $vol_name >> (Required) This is the name of the volume. =item C<< vserver_name => $vserver_name >> (Required) This is the name of the vserver the volume is on. =item C<< source_nodes => $source_nodes >> (Required) This is the list of node owners for the volume. Ex. $source_nodes = [node_name1, nodename2] =item C<< destination_nodes => $destination_nodes >> (Required) This is the intended list of node owners for the volume after the vol move. Ex. $destination_nodes = [node_name3, nodename4] =item C<< destination_aggr => $destination_aggr >> (Required) This is the intended destination aggr for the volume. =item C<< source_aggr => $source_aggr >> (Required) This is the current aggr for the volume. =item C<< local-nodes => $local_nodes >> (Optional) This specifies that the local nodes after a vol move, the destination nodes, will be added as a reporting node for each lun on the volume. This a boolean value (0/1). =item C<< destination_volume => $destination_volume >> (Optional) This specifies that the nodes for the volume will be added as a reporting node for each lun on the volume. =item C<< all => $bool >> (Optional) This specifies that all nodes in the cluster will be added as a reporting node for each lun on the volume. =item C<< add-nodes => $add_nodes >> (Optional) This is a list of nodes to be added as reporting nodes for each lun on the volume. This a boolean value (0/1). Ex. $add_nodes = [node_name3, nodename4] =item C<< remove-remote-nodes => $bool >> (Optional) This specifies that all remote nodes for will be for each lun on the volume will be removed as a reporting node. =item C<< remove-nodes => $remove_nodes >> (Optional) This specifies a list of nodes which will be for removed as a reporting lun for each lun on the volume. Ex. $remove_nodes = [node_name1, nodename2] =item C<< build_lun_metadata => $build_flag >> (Optional) { { Vserver1 => { Vol1 => { Lun1->{path}='/vol/vol1/lun1' Lun1->{igroup} = 'igroup1' Lun1->{vserver} = 'vserver' Lun1->{volume} = 'Vol1' Lun1->{node_history} => {previous_history => {local_nodes => [node1,node2] remote_nodes => [node3,node4]} current_history => {local_nodes => [node3,node4] remote_nodes => [node1,node2]} } Lun1->... {... Details for Lun2 ...}, }, {... Details for Vol2 ...}, }, }, { Vserver2 => { {... Details for Vol2 ...}, {... Details for Vol3 ...}, }, }, { Vserver3 => { {... Details for Vol1 ...}, }, }, {...}, } This specifies whether the metadata, which includes node history for each lun, should be created. This value is a 0/1 boolean value. The Volume and Lun details in the example are not hardcoded defaults. This data is example data that is intended to illustrate the lun metadata hash once populated. =back =back =cut =head2 add_reporting_nodes_for_all_volume_luns # Example for VolumeLunMapping Create my $volume_lun_mapping_obj = NACL::MTask::VolumeLunMapping->add_reporting_nodes_for_all_volume_luns(); (Instance method) This method is designed to add reporing nodes for all luns on all volumes on all vservers the user defines. The user defines the selection of volumes in the 'volumes' paramter passed to the create method. =cut =head2 remove_reporting_nodes_for_all_volume_luns # Example for VolumeLunMapping Create my $volume_lun_mapping_obj = NACL::MTask::VolumeLunMapping->remove_reporting_nodes_for_all_volume_luns(); (Instance method) This method is designed to remove reporing nodes for all luns on all volumes on all vservers the user defines. The user defines the selection of volumes in the 'volumes' paramter passed to the create method. =cut package NACL::MTask::VolumeLunMapping; use strict; use warnings; use base qw/NACL::MTask::MTask/; use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use NACL::STask::STask; use NACL::C::Volume; use NACL::C::Lun; use NACL::C::LunMapping; use NACL::C::Vserver; use NACL::ComponentUtils qw(Dumper); use NATE::Exceptions::Argument qw(:try); use Params::Validate qw(validate_with SCALAR SCALARREF ARRAYREF HASHREF BOOLEAN OBJECT); use NACL::APISet::Exceptions::CommandFailedException; use NACL::APISet::Exceptions::ResponseException; use NATE::BaseException; use Class::MethodMaker [ new => ['-hash', '-init', 'new'], scalar => [ {-type => 'NACL::C::CommandInterface::ONTAP'}, 'command_interface' ], array => 'volumes', hash => 'pv_lun_metadata_hash', scalar => 'build_lun_metadata', array => 'volume_luns', ]; ############################################################################################################ # Name : init # Description : Use the init method to validate that required options were passed in adn create base metadata ############################################################################################################ sub init { $Log->enter() if $may_enter; my ($pkg, @params) = @_; $Log->debug( sub {"This is the opts in the add_reporting_nodes_for_all_nodes method " . Dumper( \@params )}) if($Log->may_debug()); validate_with( params => \@params, spec => { %{NACL::C::Component->_common_validate_spec() }, volumes => { type => ARRAYREF , optional => 0,}, build_lun_metadata => { type => SCALAR , default => 0}, }, allow_extra => 1, ); $pkg->_create_base_metadata_hash(); $Log->exit() if $may_exit; } ############################################################################################################ # Name : add_reporting_nodes_for_all_volume_luns # Description : This method is designed to add reporting nodes for all luns on all volumes specified # within the 'volumes' parameter. ############################################################################################################ sub add_reporting_nodes_for_all_volume_luns { $Log->enter() if $may_enter; my $pkg = shift; foreach my $lun (@{$pkg->volume_luns}) { # Pull the volume name for the current lun from the lun path my $lun_path = $lun->path(); my $vol_name_regex = '\/vol\/(\S+)/'; $lun_path =~ $vol_name_regex; my $lun_volume = $1; my $lun_name = $lun->lun(); my $lun_vserver = $lun->vserver(); my $source_nodes = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'source_nodes'}; my $dest_nodes = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'destination_nodes'}; my $source_aggr = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'source_aggr'}; my $dest_aggr = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'destination_aggr'}; my $vol_meta_ref = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}; # Create pkg lun metadata reference at the lun level my $pkg_meta_ref = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{$lun_name}->{lun_metadata}; # Get opt for specifying the location to move the lun my %destination_opt = %{$pkg->_get_node_destination_opt($vol_meta_ref)}; # Add reporting nodes for the lun NACL::C::LunMapping->add_reporting_nodes(command_interface => $pkg->command_interface, igroup => $pkg_meta_ref->{igroup}, vserver => $pkg_meta_ref->{vserver}, path => $pkg_meta_ref->{path}, %destination_opt, ); # Modify the lun_metadata if ($pkg->build_lun_metadata) { # Update lun_metadata_item's node history $pkg_meta_ref->{node_history}->{current_history}->{local_nodes} = $dest_nodes; $pkg_meta_ref->{node_history}->{current_history}->{remote_nodes} = $source_nodes; $pkg_meta_ref->{node_history}->{current_history}->{previous_aggr} = $source_aggr; $pkg_meta_ref->{node_history}->{current_history}->{current_aggr} = $dest_aggr; # Update lun_metadata_item's node history # If remove_reporting nodes has been called first then there are no reporting nodes for the orignal node history if (exists $pkg_meta_ref->{node_history}->{current_history}->{removed_nodes} ) { $pkg_meta_ref->{node_history}->{previous_history}->{remote_nodes} = undef; delete $pkg_meta_ref->{node_history}->{current_history}->{removed_nodes}; } else { $pkg_meta_ref->{node_history}->{previous_history}->{remote_nodes} = $dest_nodes; } $pkg_meta_ref->{node_history}->{previous_history}->{local_nodes} = $source_nodes; $pkg_meta_ref->{node_history}->{previous_history}->{previous_aggr} = undef; $pkg_meta_ref->{node_history}->{previous_history}->{current_aggr} = $source_aggr; } # end of if } # end of foreach $Log->exit() if $may_exit; if ($pkg->build_lun_metadata) { return $pkg->pv_lun_metadata_hash; } else { # Return Empty Hash if no metadata requested return {}; } } ############################################################################################################ # Name : remove_reporting_nodes_for_all_volume_luns # Description : This method is designed to remove reporting nodes for all luns on all volumes specified # within the 'volumes' parameter. ############################################################################################################ sub remove_reporting_nodes_for_all_volume_luns { $Log->enter() if $may_enter; my $pkg = shift; foreach my $lun (@{$pkg->volume_luns}) { # Pull the volume name for the current lun from the lun path my $lun_path = $lun->path(); my $vol_name_regex = '\/vol\/(\S+)/'; $lun_path =~ $vol_name_regex; my $lun_volume = $1; my $lun_name = $lun->lun(); my $lun_vserver = $lun->vserver(); my $source_nodes = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'source_nodes'}; my $dest_nodes = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'destination_nodes'}; my $source_aggr = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'source_aggr'}; my $dest_aggr = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}->{'destination_aggr'}; my $vol_meta_ref = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{'vol_metadata'}; # Create pkg lun metadata reference at the lun level my $pkg_meta_ref = $pkg->pv_lun_metadata_hash->{$lun_vserver}->{$lun_volume}->{$lun_name}->{lun_metadata}; # Get opt for specifying the location to move the lun my %removal_opt = %{$pkg->_get_node_removal_opt($vol_meta_ref)}; # Remove reporting nodes for each lun NACL::C::LunMapping->remove_reporting_nodes(command_interface => $pkg->command_interface, igroup => $pkg_meta_ref->{igroup}, vserver => $pkg_meta_ref->{vserver}, path => $pkg_meta_ref->{path}, %removal_opt, ); # Modify the lun_metadata if ($pkg->build_lun_metadata) { # If a nodes history already exists for this lun update it, if not record current state with a list of removed nodes if (exists($pkg_meta_ref->{node_history})) { # Update lun_metadata_item's node history my @local_copy = @{$pkg_meta_ref->{node_history}->{current_history}->{local_nodes}}; my @remote_copy = @{$pkg_meta_ref->{node_history}->{current_history}->{remote_nodes}}; $pkg_meta_ref->{node_history}->{previous_history}->{local_nodes} = \@local_copy; $pkg_meta_ref->{node_history}->{previous_history}->{remote_nodes} = \@remote_copy; $pkg_meta_ref->{node_history}->{previous_history}->{previous_aggr} = $pkg_meta_ref->{node_history}->{current_history}->{previous_aggr}; $pkg_meta_ref->{node_history}->{previous_history}->{current_aggr} = $pkg_meta_ref->{node_history}->{current_history}->{current_aggr}; # Remove Remote Nodes from lun's current node history $pkg_meta_ref->{node_history}->{current_history}->{removed_nodes} = $source_nodes; $pkg_meta_ref->{node_history}->{current_history}->{remote_nodes} = undef; } else { $pkg_meta_ref->{node_history} = {current_history => {removed_nodes => $source_nodes,} }; } } # end of if } # end of foreach $Log->exit() if $may_exit; if ($pkg->build_lun_metadata) { return $pkg->pv_lun_metadata_hash; } else { # Return Empty Hash if no metadata requested return {}; } } ############################################################################################################ # Name : _create_base_metadata_hash # Description : Returns a hash containing the the vservers and volumes an luns which lun mapping will be modified for. ############################################################################################################ sub _create_base_metadata_hash { $Log->enter() if $may_enter; my $pkg = shift; my $volumes = pop($pkg->volumes); my $lun_metadata_hash; my @vol_luns; my @lun_mappings; # If the the lun_metadata hash already exists, don't waste time building the hash heirarchy, it will already be populated # Build the arguments for the LunMapping command based on the vol_hash provided by the user. # The groundwork for storing the lun metadata will also be laid. # The hash structure for %lun_metadata_hash will be constructed like so. # { # Vserver1->%Vol1->$vol1_meta # Vserver2->%Vol2->$vol2_meta # ->%Vol3->$vol3_meta # Vserver3->$Vol1->$vol1_meta # ... # } foreach my $vol_item (@{$volumes}) { my $vol_name = $vol_item->{vol_name}; my $vserver_name = $vol_item->{vserver_name}; # Add the volume metadata for the volumes in the lun_metadata_hash $lun_metadata_hash->{$vserver_name}->{$vol_name}->{'vol_metadata'} = {source_nodes => $vol_item->{source_nodes}, destination_nodes => $vol_item->{destination_nodes}, source_aggr => $vol_item->{source_aggr}, destination_aggr => $vol_item->{destination_aggr}, 'local-nodes' => $vol_item->{'local-nodes'}, 'destination-volume' => $vol_item->{'destination-volume'}, 'all' => $vol_item->{all}, 'add-nodes' => $vol_item->{nodes}, 'remove-remote-nodes' => $vol_item->{'remove-remote-nodes'}, 'remove-nodes' => $vol_item->{'remove-nodes'}, vol_name => $vol_name, vserver => $vserver_name, }; } # Set Created hash to pkg metadata $pkg->pv_lun_metadata_hash(%{$lun_metadata_hash}); # Fetch all luns and lun mappings for all specified volumes on all specified vservers. This ensures no unspecified # luns or lun mappings are gathered and prevents command failures by limiting number of volumes to query to 10. my $max_query_count; my $volumes_filter; my @temp_luns; my @temp_mappings; foreach my $vserver (keys($lun_metadata_hash)) { my @vol_keys = keys($lun_metadata_hash->{$vserver}); $volumes_filter = ""; $max_query_count = 0; foreach my $volume (@vol_keys) { $max_query_count++; $volumes_filter .= "$volume|"; if ($max_query_count == 10) { # Remove trailing pipe $volumes_filter =~ s/\|$//; @temp_luns = NACL::C::Lun->find(command_interface => $pkg->command_interface, filter => {volume => $volumes_filter, vserver => $vserver, isMapped => 'true', }, ); @temp_mappings = NACL::C::LunMapping->find(command_interface => $pkg->command_interface, filter => {volume => $volumes_filter, vserver => $vserver, }, ); push(@lun_mappings, @temp_mappings); push(@vol_luns, @temp_luns); $max_query_count = 0; } } # Run the command if the last set of vserver volumes to query is less than 10 if($max_query_count < 10) { # Remove trailing pipe $volumes_filter =~ s/\|$//; @temp_luns = NACL::C::Lun->find(command_interface => $pkg->command_interface, filter => {volume => $volumes_filter, vserver => $vserver, isMapped => 'true', }, ); @temp_mappings = NACL::C::LunMapping->find(command_interface => $pkg->command_interface, filter => {volume => $volumes_filter, vserver => $vserver, }, ); push(@lun_mappings, @temp_mappings); push(@vol_luns, @temp_luns); $max_query_count = 0; } } # Set discovered luns to pkg $pkg->volume_luns(@vol_luns); unless(defined(@{$pkg->volume_luns})) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw("No luns were detected for removal of reporting nodes. It's likely the 'volumes' " . "parameter was specified incorrectly. Please, refer to the perl doc for proper " . "formatting of the 'volumes' parameter."); } # Match Luns to mappings and add the igroup it's mapped to to the lun metadata foreach my $mapping (@lun_mappings) { foreach my $lun (@vol_luns) { my $lun_path = $lun->path(); my $vol_name_regex = '\/vol\/(\S+)/'; $lun_path =~ $vol_name_regex; my $lun_volume = $1; my $lun_name = $lun->lun(); my $lun_vserver = $lun->vserver(); my $lun_igroup = $mapping->igroup(); if ($lun->path() eq $mapping->path() and $lun->vserver() eq $mapping->vserver()) { # Create pkg lun metadata reference at the lun level my $meta_ref = $pkg->pv_lun_metadata_hash; my $lun_meta = $meta_ref->{$lun_vserver}->{$lun_volume}; # Define the metadata values for each lun $lun_meta->{$lun_name}->{lun_metadata}->{path} = $lun_path; $lun_meta->{$lun_name}->{lun_metadata}->{igroup} = $lun_igroup; $lun_meta->{$lun_name}->{lun_metadata}->{vserver} = $lun_vserver; last; } } } $Log->exit() if $may_exit; } ############################################################################################################ # Name : _get_node_destination_opt # Description : Returns a hash containing the destination nodes for the add_reporting_nodes component call. ############################################################################################################ sub _get_node_destination_opt { $Log->enter() if $may_enter; my ($pkg, $vol_metadata) = @_; # Opt for specifying the location to move each lun my %destination_opt; # Get the destination parameter for the lun from the parent volume if (defined($vol_metadata->{'local-nodes'})) { $destination_opt{'local-nodes'} = $vol_metadata->{'local-nodes'}; } elsif (defined($vol_metadata->{'destination-volume'})) { $destination_opt{'destination-volume'} = $vol_metadata->{'destination-volume'}; } elsif (defined($vol_metadata->{all})) { $destination_opt{all} = $vol_metadata->{'all'}; } elsif (defined($vol_metadata->{'add-nodes'})) { $destination_opt{nodes} = $vol_metadata->{'add-nodes'}; } elsif (defined($vol_metadata->{destination_aggr})) { $destination_opt{'destination-aggregate'} = $vol_metadata->{destination_aggr}; } if (not %destination_opt) { # Error out. "The metadata for the volume was not provided correctly. A destination paramater was not provided $Log->exit() if $may_exit; NATE::BaseException->throw( "The metadata for the volume was not provided correctly. A destination paramater such as 'nodes' or " . "destination-aggregate was not provided." ); } # end of if $Log->exit() if $may_exit; return \%destination_opt; } ############################################################################################################ # Name : _get_node_removal_opt # Description : Returns a hash containing the nodes for the remove_reporting_nodes component call. ############################################################################################################ sub _get_node_removal_opt { $Log->enter() if $may_enter; my ($pkg, $vol_metadata) = @_; # Opt for specifying the location to move each lun my %removal_opt; # Get the destination parameter for the lun from the parent volume if (defined($vol_metadata->{'remove-remote-nodes'})) { $removal_opt{'remote-nodes'} = $vol_metadata->{'remove-remote-nodes'}; } elsif (defined($vol_metadata->{'remove-nodes'})) { $removal_opt{'nodes'} = $vol_metadata->{'remove-nodes'}; } if (not %removal_opt) { # Error out. "The metadata for the volume was not provided correctly. A destination paramater was not provided $Log->exit() if $may_exit; NATE::BaseException->throw( "The metadata for the volume was not provided correctly. No nodes were specified for removal." ); } # end of if $Log->exit() if $may_exit; return \%removal_opt; } 1;