# # Copyright (c) 2001-2013 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary NASTraffic Task Module ## @author nayankumar.patel@netapp.com, dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::MTask::NASTraffic =head1 SYNOPSIS use NACL::MTask::NASTraffic; # start NFS traffic on CMode with sio (sio is default tool with default options) my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node, volume => 'exh6_vol1', server => '198.18.5.73', vserver => 'vs1', 'home-port' => 'e7a', ## needed if lif not exists 'home-node' => $Node->node(), ## needed if lif not exists lif => 'datalif1', ## lif needed if not created already. netmask => '255.255.255.0', ## needed if lif not created already gateway => '198.18.5.39', ## needed if route is not created. 'junction-path' => '/exh6_vol1', }, 'host_info' => [ { command_interface => $Client, } ], nacltask_traffic_type => 'nfs', nacltask_traffic_direction => 'write', ); # start NFS traffic on 7Mode my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node, volume => 'vol1', server => '198.18.5.237', }, 'host_info' => [ { command_interface => $Client, } ], nacltask_traffic_type => 'nfs', nacltask_traffic_direction => 'read', ); # start NFS IPv6 Traffic on 7Mode my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node, volume => 'vol1', server => '1111::1', }, 'host_info' => [ { command_interface => $Client, } ], nacltask_traffic_type => 'nfs', nacltask_traffic_direction => 'read', ); # start CIFS traffic on CMode with sio my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node, volume => 'exh6_vol1', 'share' => 'exh6_share1', server => '198.18.5.73', vserver => 'vs1', domain => 'NICQA.lab.netapp.com', ## need FQDN, 'cifs-server' => 'CORDUROY_CIFS', username => 'Administrator', password => 'sundance' }, 'host_info' => [ { command_interface => $Client, } ], nacltask_traffic_type => 'cifs', nacltask_traffic_direction => 'read', ); # start CIFS traffic on 7Mode with sio my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node, volume => 'vol1', server => '198.18.5.237', }, 'host_info' => [ { command_interface => $Client, } ], nacltask_traffic_type => 'cifs', nacltask_traffic_direction => 'write', ); # start nfs traffic on CMode with Hammer my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node, volume => 'exh6_vol1', server => '198.18.5.73', vserver => 'vs1', 'home-port' => 'e7a', # needed if lif not exists 'home-node' => $Node->node(), # needed if lif not exists lif => 'datalif1', ## lif needed if not created already. netmask => '255.255.255.0', ## needed if lif not created already gateway => '198.18.5.39', ## needed if route is not created. 'junction-path' => '/exh6_vol1', }, 'host_info' => [ { command_interface => $Client, tool => 'hammer', 'tool_options' => { 'nofilercore' => 1, } } ], nacltask_traffic_type => 'nfs', ); # stop only traffic without cleanup $obj->stop_only_traffic(); # start only traffic without setup $obj->start_only_traffic(); # stop all traffic and perfrom cleanup $obj->stop(); =head1 DESCRIPTION C provides method to start and stop NFS/CIFS data traffic. It supports IPv4 and IPv6 address. it supports following machine type: =over =item 1. Filer (Both 7Mode and CMode) =item 2. Client (all supported by C and C) =back =cut package NACL::MTask::NASTraffic; 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 Params::Validate qw(validate_with ARRAYREF HASHREF SCALAR OBJECT); use NATE::Exceptions::Argument qw(:try); use NATE::BaseException qw(:try); use NACL::MTask::NFS; use NACL::MTask::CIFS; use NACL::STask::VserverCifsShare; use NACL::STask::Client::Mount; use NACL::MTask::Traffic; use NACL::Exceptions::TrafficFailed; use NACL::STask::STask; use NACL::ComponentUtils qw(_verify_invocation); use NACL::CS::Volume; =head1 METHODS =head2 new my $NASTraffic_obj = NACL::MTask::NASTraffic->new ( 'node_info' => { command_interface => $node_command_interface, vserver => 'vs1', 'cifs-server' => "cifs1", username => "admnistrator", domain => "NICQA.lab.netapp.com", password => "sundance", share => $share_name, volume => "vol1", server => '10.10.10.10', }, 'host_info' => [ { command_interface => $host_command_interface, }, ], ); Return a new NACL::MTask::NASTraffic object with the given attributes. =cut use Class::MethodMaker [ new => [ '-hash', 'new' ], scalar => 'host_info', scalar => 'node_info', ]; =head2 start (Class method) This method run traffic based on information provided to it. it will raise exception if setup failed on any machine. my $obj = NACL::MTask::NASTraffic->start( 'node_info' => { command_interface => $Node_Command_Interface, volume => $volume, server => $server_ip_address, share => $share, # Only for CIFS domain => $Fully_Qualified_Domain_Name, # Only for CIFS username => $username, # Only for CIFS password => $password, # Only for CIFS %Any_Options_handle_by_NFS_or_CIFS_setup_other_then_above }, 'host_info' => [ { command_interface => $Client_Command_Interface, tool => $tool, # default is 'sio' 'tool_options' => { %tool_APISet_Options, # defailt for 'sio' is provide below }, %Any_Options_for_Mount_Task, } ], nacltask_traffic_type => 'nfs', nacltask_traffic_direction => 'write', # only supported for 'sio' ); it will perform following operations: =over =item 1. setup NFS/CIFS on filer It will use C and C to perform this operation. default and Required value of options to these two task are listed in Options section. =item 2. Mount Volume/share on client Task will assume that client has necessary IP address configured. Task will automatically generate c parameter for C based on filer mode(7Mode or CMode). mount_point will be auto generated as described in C. additional option can be provided to C by adding them to C parameter. =item 3. Run traffic on mount point. Task will run traffic using C. Options to C is described in Options section. it will use "sio" as a default tool. =back =over =item Options =over =item C<< node_info => \%node_information >> (Required) It contains information about filer. 'node_info' => { command_interface => $Node_Command_Interface, volume => $volume, server => $server_ip_address, share => $share, # Only for CIFS 'cifs-server' => $cifs_server_name, # Only for CIFS domain => $Fully_Qualified_Domain_Name, # Only for CIFS username => $username, # Only for CIFS password => $password, # Only for CIFS %Any_Options_handle_by_NFS_or_CIFS_setup_other_than_above }, command_interface => $command_interface (Required) command_interface of node/filer. server => $server_ip_address (Required) server ip address. it will map to c parameter for C. it will be use as c
parameter to C volume => $volume (Required) it can be name of volume or volume object of type C. it will map to c parameter for C. share => $share (Required For CIFS only) cifs share name or object of type C. it will map to c parameter for C. cifs-server => $cifs-server (Required For CIFS CMode, CIFS 7Mode Optional) see C. domain => $Fully_Qualified_Domain_Name (Required For CIFS CMode, Optional for 7Mode), it will map to c parameter for C. For 7Mode, if not specified it will default to 'WORKGROUP' username => $username (Required For CIFS only) see C. it will map to c parameter for C, if on 7Mode. default to 'Administrator' password => $password (Required For CIFS only) see C. it will map to c parameter for C, if on 7Mode. default to default_password from nate_host file. Following will be the defaults for NACL::MTask::NFS->setup() options: nacltask_if_exists => 'reuse' nacltask_if_lif_exists => 'reuse' nacltask_if_mount_exists => 'reuse' nacltask_if_exportpolicyrule_exists => 'reuse' nacltask_if_exportpolicy_exists => 'reuse' nacltask_if_routinggrouproute_exists => 'reuse' Following will be the defaults for NACL::MTask::CIFS->setup() options: if_exists => 'reuse' along with above options, user can pass any other options to this task if it is accepted(require or optional) by C or C. =item C<< host_info => \@host_information >> (Required) It contains information about clients. 'host_info' => [ { command_interface => $host_command_interface, tool => $tool, 'tool_options' => $APISet_options_for_tool %options_For_Mount_Task }, ], command_interface => $command_interface (Required) command_interface of client. tool => $tool see C. default to "sio" tool_options => $APISet_options_for_tool It is a reference to a HASH. it contains APISet Options for the tool. The default tool is "sio", so this parameter has following default value for "sio" APISet options. 'tool_options' => { 'read' => 0, 'rand' => 100, block_size => "256k", start_byte => 0, file_size => "1g", num_of_threads => 5, direct => 1, }; if user set 'read' option for sio, it won't be considered. it will always be 100% read. user can provide read operation by setting c to 'read'. %options_For_Mount_Task Following Mount Task options are handle by Task itself, so user doesn't need to provide them. user can provide rest of the options for C. 1. server 2. path 3. volume 4. share =item C<< nacltask_traffic_type => $traffic_type >> (Required) Type of Traffic. it could be "nfs" or "cifs" =item C<< nacltask_traffic_direction => $traffic_direction >> (Optional) direction of traffic. it could be "read" or "write". this option is only supported for 'sio' tool. if this option is provided with other tool then it will throw exception. default to 'write'. =item C< $size >> (Optional) This is the size of the file which is required for the sio operation. ( Default : (0.3 * Volume-size )) =back =back =cut sub start { $Log->enter() if $may_enter; my $pkg = shift; $Log->debug( "Opts to 'start':\n" . Dumper( \@_ ) ) if ( $Log->may_debug() ); my %opts = validate_with( params => \@_, spec => { node_info => { type => HASHREF }, host_info => { type => ARRAYREF }, sio_option_filesize => { type => SCALAR, optional => 1 }, nacltask_traffic_type => { type => SCALAR, regex => qr/(nfs|cifs)/i }, nacltask_traffic_direction => { type => SCALAR, default => 'write' } }, allow_extra => 1 ); my $traffic_type = delete $opts{nacltask_traffic_type}; my $direction = delete $opts{nacltask_traffic_direction}; my $node_info = {}; %{$node_info} = validate_with( params => $opts{'node_info'}, spec => { command_interface => { type => OBJECT, isa => "NACL::C::CommandInterface" }, server => { type => SCALAR }, volume => { type => SCALAR | OBJECT, optional => 1 }, share => { type => SCALAR | OBJECT, optional => 1 }, }, allow_extra => 1, ); my $host; my $hosts_info = []; my $count = 0; my %temp = (); # only store require parameter to Task Object my $node_hash = {}; my $host_hash = []; my $actual_file_size; if( ! $opts{sio_option_filesize} ) { my ($convert_byte,$vol_size_in_bytes) ; my $vol_obj = NACL::CS::Volume->fetch( command_interface => $node_info->{command_interface}, filter => { volume => $node_info->{volume}, }, requested_fields => [ qw( total ) ], ); $vol_size_in_bytes = $vol_obj->total(); #File size to be used for sio operation will be 30% of the total volume size. my $file_size_in_bytes = (0.3) * $vol_size_in_bytes; ## Fix for burt 737898 ## If the calculated file size is greater that 1gb, the subsequent read ## operation fails as the file is fully not written in 60 seconds. ## Hence restricting the file size to be 1gb in case calculated file size ## is greater than 1gb;The library originally was using 1gb file size by ## default. But since it was erroring out for volumes less than 1gb, the ## new code to calculate 30% of vol size to be default file size was introduced ## recently. This is causing backward incompatibility to scripts using large ## volumes say 500gb, as file writes takes longer. ## For complete discussion, see the burt page. my $one_gb_in_bytes = '1073741824'; if ( $file_size_in_bytes > $one_gb_in_bytes ) { $actual_file_size = "1024mb"; } else { my $file_size_in_mb = int( $file_size_in_bytes / ( 1024 * 1024 ) ); $actual_file_size = $file_size_in_mb . "mb"; } } else { $actual_file_size = $opts{sio_option_filesize}; } my $default_sio_options = { 'read' => 0, 'rand' => 100, block_size => "256k", start_byte => 0, file_size => $actual_file_size, num_of_threads => 5, direct => 1, }; foreach $host ( @{ $opts{'host_info'} } ) { %temp = validate_with( params => $host, spec => { command_interface => { type => OBJECT, isa => "NACL::C::CommandInterface" }, tool => { type => SCALAR, default => 'sio' }, 'tool_options' => { type => HASHREF, optional => 1 }, }, allow_extra => 1 ); if ( $temp{tool} !~ /sio/ && !$temp{'tool_options'} ) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "tool_options parameter is a required param for $temp{tool}"); } if ( $temp{tool} =~ /sio/ ) { if ( $temp{'tool_options'} ) { # delete read option from user delete $temp{'tool_options'}->{read}; # merge user provided value with default %{ $temp{'tool_options'} } = ( %{$default_sio_options}, %{ $temp{'tool_options'} } ); } else { $temp{'tool_options'} = $default_sio_options; } } $hosts_info->[ $count++ ] = \%temp; } my $mount_options = {}; $pkg->_hash_move( source => $node_info, target => $mount_options, move => [qw(volume share server)], ); if ( $traffic_type =~ /nfs/i ) { $Log->trace("Setup NFS on Filer"); my $nfs_setup_options = { nacltask_if_exists => 'reuse', nacltask_if_lif_exists => 'reuse', nacltask_if_routinggrouproute_exists => 'reuse', nacltask_if_exportpolicy_exists => 'reuse', nacltask_if_exportpolicyrule_exists => 'reuse', nacltask_if_mount_exists => 'reuse' }; if ( $node_info->{command_interface}->mode() eq "7Mode" ) { $nfs_setup_options->{options} = "sec=sys,rw,anon=0,nosuid"; } $node_hash->{nfs_obj} = NACL::MTask::NFS->setup( %{$nfs_setup_options}, %{$node_info}, address => $mount_options->{server}, volume => $mount_options->{volume}, ); $mount_options->{volume} = $node_hash->{'nfs_obj'}->volume(); } elsif ( $traffic_type =~ /cifs/i ) { my %share_create_options = (); # set user and password parameter for CIFS mount $pkg->_process_cifs_mount_options( $node_info, $mount_options ); # domain, username and password # all 3 are apply to both Mode. and mapped accordingly # map the domain, username and password based on ONTAP Mode. my $cifs_setup_options = $pkg->_process_cifs_options($node_info); $cifs_setup_options->{if_exists} = "reuse"; $Log->trace("Setup CIFS on Filer"); if ( $node_info->{command_interface}->mode() eq "CMode" ) { # setup CIFS on C-Mode $node_hash->{cifs_obj} = NACL::MTask::CIFS->setup( %{$cifs_setup_options}, %{$node_info}, ); $share_create_options{vserver} = $node_info->{vserver}; } elsif ( $node_info->{command_interface}->mode() eq "7Mode" ) { $node_info->{'auth-type'} = 'passwd' unless ( $node_info->{'auth-type'} ); # setup CIFS on 7Mode $node_hash->{cifs_obj} = NACL::MTask::CIFS->setup_7Mode( %{$cifs_setup_options}, %{$node_info}, ); } else { $Log->exit() if $may_exit; NATE::BaseException->throw( "Filer mode '" . $node_info->{command_interface}->mode() . "' not supported." ); } if ( !$mount_options->{share} ) { $mount_options->{share} = "share_" . time(); } $node_hash->{share_was_created} = 0; unless ( ref( $mount_options->{share} ) ) { # share create need volume so provide volume name if ( ref( $mount_options->{volume} ) ) { $share_create_options{volume} = $mount_options->{volume}->volume(); } else { $share_create_options{volume} = $mount_options->{volume}; } # create CIFS share $node_hash->{cifs_share_obj} = NACL::STask::VserverCifsShare->create( command_interface => $node_info->{command_interface}, %share_create_options, 'share-name' => $mount_options->{share}, nacltask_if_exists => 'reuse', nacltask_verify => 0, _was_created => \$node_hash->{share_was_created}, ); # copy share object to mount $mount_options->{share} = $node_hash->{'cifs_share_obj'}; } # delete volume from mount options becasue we added share object to it. delete $mount_options->{volume}; } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "nacltask_traffic_type must be nfs or cifs."); } my ( $path, $tool, $ci, $tool_options, $apiset, $mount_obj, $traffic_obj ); foreach $host ( @{$hosts_info} ) { $ci = delete $host->{command_interface}; $tool = delete $host->{tool}; $tool_options = delete $host->{'tool_options'}; # if user provide 'server' and 'volume' remove it. # Task will handle these options. user don't need to provide it. delete $host->{server}; delete $host->{volume}; delete $host->{share}; $Log->trace("Mount Volume/share on Client"); $mount_obj = NACL::STask::Client::Mount->create( command_interface => $ci, 'chmod' => '777', %{$mount_options}, %{$host}, ); # get mount to run traffic $path = $mount_obj->mount_point(); # sio need file to read or write # create file if tool is sio if ( $tool =~ /sio/ ) { my $rand = NACL::STask::STask->random_name_generator(); $path = "$path/file_$rand"; $apiset = $ci->apiset(); $apiset->execute_raw_command( 'command' => "echo xyz > " . $path ); } $Log->trace("Start Traffic on Mount Point"); $traffic_obj = NACL::MTask::Traffic->start( command_interface => $ci, paths => $path, tool => $tool, %{$tool_options} ); if ( $tool =~ /sio/ && $direction =~ /read/i ) { # it is a read operation. so we will populate data first using # write operation. write operation will keep writing data for # 60 seconds. Tharn::snooze 60; # Stop write operation after 60 seconds. $traffic_obj->stop(); # start read operation. $tool_options->{read} = 100; $traffic_obj->start( tool => $tool, %{$tool_options} ); } push @{$host_hash}, { mount_obj => $mount_obj, traffic_obj => $traffic_obj, tool => $tool, tool_options => $tool_options, 'host' => $ci->client(), traffic_started => 1, ## To know that traffic is running }; } my $obj = $pkg->new( 'node_info' => $node_hash, 'host_info' => $host_hash, ); $Log->exit() if $may_exit; return $obj; } # end of start sub =head2 stop $data_traffic_object->stop(); (Instance Method) This Method stops the data traffic. =cut sub stop { $Log->enter() if $may_enter; my $obj = shift; my $hosts_info = $obj->host_info(); my $node_info = $obj->node_info(); foreach my $host ( @{$hosts_info} ) { if ( $host->{traffic_started} ) { $Log->trace("Stop Traffic from $host->{host}"); $host->{traffic_obj}->stop(); } # wait for 5 seconds to stop traffic Tharn::snooze 5; $Log->trace("Unmount Volume"); $host->{mount_obj}->purge( force => 1, retries => 10 ); } if ( defined( $node_info->{nfs_obj} ) ) { $Log->trace("Cleanup NFS"); $node_info->{nfs_obj}->purge(); } elsif ( defined( $node_info->{cifs_obj} ) ) { $Log->trace("Cleanup CIFS"); if ( defined( $node_info->{'cifs_share_obj'} ) && $node_info->{share_was_created} ) { $node_info->{'cifs_share_obj'}->purge(); } } $Log->exit() if $may_exit; } ## end sub stop =head2 stop_only_traffic # stop traffic from all host $NASTraffic_object->stop_only_traffic(); # stop traffic from specific host $NASTraffic_object->stop_only_traffic(hosts => ['$host1_ci','$host2_ci']); (Instance Method) This Method will only stops the data traffic from host. it won't perform any cleanup operations. It will simply kill the process, running the traffic tool. =over =item Options =item C<< hosts => \@hosts_command_interface >> (Optional) list of command_interface of the hosts from which user want to stop traffic. =back =cut sub stop_only_traffic { $Log->enter() if $may_enter; my $obj = shift; $obj->_verify_invocation( style => 'instance_only' ); my %opts = validate_with( params => \@_, spec => { hosts => { type => ARRAYREF, optional => 1 }, }, allow_extra => 1, ); my $hosts = $obj->host_info(); my $selected_hosts = []; my $host; if ( defined( $opts{hosts} ) ) { foreach $host ( @{ $opts{hosts} } ) { if ( $host->isa('NACL::C::Client') ) { push @{$selected_hosts}, grep( $_->{host} eq $host->client(), @{$hosts} ); } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Received object of type". ref($host) ." but an object of ". "type NACL::C::Client or its subclasses is expected in " . "hosts parameter"); } } } else { $selected_hosts = $hosts; } foreach $host ( @{$selected_hosts} ) { $Log->trace("Stop Traffic from $host->{host}"); if ( $host->{traffic_started} ) { $host->{traffic_obj}->stop(); $host->{traffic_started} = 0; } else { $Log->trace("Traffic is already stopped from $host->{host}"); } } $Log->exit() if $may_exit; } ## end sub stop_only_traffic =head2 start_only_traffic # start traffic from all host $NASTraffic_object->start_only_traffic(); # start traffic from specific host $NASTraffic_object->start_only_traffic(hosts => ['$host1_ci','$host2_ci']); (Instance Method) This Method will only start the data traffic from host. it won't perform any setup operations. It will simply spawn the process, which runs the traffic tool. =over =item Options =item C<< hosts => \@hosts_command_interface >> (Optional) list of command_interface of the hosts from which user want to start traffic. =back =cut sub start_only_traffic { $Log->enter() if $may_enter; my $obj = shift; $obj->_verify_invocation( style => 'instance_only' ); my %opts = validate_with( params => \@_, spec => { hosts => { type => ARRAYREF, optional => 1 }, }, allow_extra => 1, ); my $hosts = $obj->host_info(); my $selected_hosts = []; my $host; if ( defined( $opts{hosts} ) ) { foreach $host ( @{ $opts{hosts} } ) { if ( $host->isa('NACL::C::Client') ) { push @{$selected_hosts}, grep( $_->{host} eq $host->client(), @{$hosts} ); } else { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Received object of type". ref($host) ." but an object of ". "type NACL::C::Client or its subclasses is expected in " . "hosts parameter"); } } } else { $selected_hosts = $hosts; } foreach $host ( @{$selected_hosts} ) { $Log->trace("Start Traffic from $host->{host}"); unless ( $host->{traffic_started} ) { $host->{traffic_obj}->start( tool => $host->{tool}, %{ $host->{tool_options} } ); $host->{traffic_started} = 1; } else { $Log->trace("Traffic is already running from $host->{host}"); } } $Log->exit() if $may_exit; } ## end sub start_only_traffic ################################################################## # P R I V A T E M E T H O D S # ################################################################## sub _process_cifs_options { $Log->enter() if $may_enter; my $self = shift; my $args_ptr = shift; my $cifs_setup_options = {}; my %mode_map = ( '7Mode' => { domain => 'domain', usernmae => 'domain-user', password => 'domain-pass', }, 'CMode' => { domain => 'cifs_domain', username => 'username', password => 'password' }, ); $self->_hash_move( source => $args_ptr, target => $cifs_setup_options, map => $mode_map{ $args_ptr->{command_interface}->mode() } ); $Log->exit() if $may_exit; return $cifs_setup_options; } ## end sub _process_cifs_options sub _process_cifs_mount_options { $Log->enter() if $may_enter; my $self = shift; my ( $args_ptr, $mount_options ) = @_; # get domain, username and password for Mount if ( $args_ptr->{domain} ) { $args_ptr->{domain} =~ m/^(\w+)\./; $mount_options->{user} = $1; } elsif ( $args_ptr->{workgroup} ) { $mount_options->{user} = $args_ptr->{workgroup}; } else { $mount_options->{user} = 'WORKGROUP'; } if ( $args_ptr->{'username'} ) { $mount_options->{user} = $mount_options->{user} . "\\" . $args_ptr->{'username'}; } else { $mount_options->{user} = $mount_options->{user} . "\\" . 'Administrator'; } if ( $args_ptr->{'password'} ) { $mount_options->{password} = $args_ptr->{'password'}; } else { # if password not defined it will use default_password from hostspec $mount_options->{password} = $args_ptr->{command_interface}->hostrec->default_password(); } $Log->exit() if $may_exit; } ## end sub _process_cifs_mount_options 1;