# # Copyright (c) 2014 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary AggregateFile ComponentState Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::CS::AggregateFile =head1 DESCRIPTION C is a derived class of L. It represents the state of an ONTAP AggregateFile. A related class is L, which represents access to an ONTAP VolumeFile. Note that when $aggregate_file_obj->state(); is invoked a C object is returned. =head1 ATTRIBUTES Each field of output of the slist command are the attributes of the AggregateFile ComponentState. Each of the attributes listed below have accessor methods of the same name. For example, for the attribute "name", this can be done: my $name = $cs_obj->name(); Note that this CS package can dynamically add attributes at run-time, so even if a field of output is not listed below, it can still be used. =over =item C<< name >> File nameh. For example, if the file path is "/aggr/agg0/file1", the name also would be "file1", Filled in for CMode and Nodescope. =item C<< file_size >> size of the file. Filled in for CMode and Nodescope. =item C<< inode >> The inode of the file. Filled in for CMode and Nodescope. =back =cut package NACL::CS::AggregateFile; use strict; use warnings; use base 'NACL::CS::ComponentState::ONTAP'; 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(:all); use NACL::C::AggregateFile; use NACL::ComponentUtils qw(_optional_scalars); use NACL::Exceptions::NoElementsFound qw(:try); use Class::MethodMaker [ scalar => 'file_size', scalar => 'name', scalar => 'inode', scalar => 'path' ]; =head1 METHODS =head2 fetch # Scalar context my $AggregateFile_state = NACL::CS::AggregateFile->fetch( command_interface => $ci, filter => { path => $file_path } ); # List context my @AggregateFile_states = NACL::CS::VolumeFile->fetch( command_interface => $ci, filter => { path => $file_path } ); (Class method) Discovers which elements are present and returns their state in ComponentState objects. Called in scalar context it returns only one state object, in list context it returns all state objects. See L for a more detailed description along with a complete explanation of the options it accepts. For this method, C is a mandatory input in the filter. Uses a 7Mode or Nodescope CLI APISet. =over =item Exceptions =over =item C When there are no elements matching the query specified or elements of that type doesn't exist, then this exception will be thrown. =back =back =cut sub fetch { $Log->enter(); my ( $pkg, @args ) = @_; my %opts = validate( @args, $pkg->_fetch_validate_spec() ); my $path = $opts{filter}{path}; unless ( defined $path ) { $Log->exit(); NATE::Exceptions::Argument->throw( "$pkg::fetch: 'path' has to be provided in the filter for " . 'the call to work' ); } my @state_objs = $pkg->SUPER::fetch( %opts, choices => [ { method => '_fetch_7mode_cli', interface => 'CLI', set => '7Mode|Nodescope', }, ], exception_text => 'No matching AggregateFile(s) found' ); $Log->exit(); return wantarray ? @state_objs : $state_objs[0]; } sub _fetch_7mode_cli { $Log->enter(); my ( $pkg, @args ) = @_; my %opts = validate @args, $pkg->_fetch_backend_validate_spec(); my $apiset = $opts{apiset}; # Get the all the path with node-scope-name my %path_with_nodescope = $pkg->_convert_path_and_get_node( %opts ); my @state_objs; while (my ($path, $node) = each %path_with_nodescope) { my %api_args; $api_args{path} = $path; $api_args{'nodescope-node-name'} = $node if($node); try { my $response = $apiset->slist(%api_args); my $output = $response->get_parsed_output(); foreach my $row (@$output) { $row->{path} = $row->{name}; $row->{name} =~ s/.*\/(.*)$/$1/; my $obj = $pkg->new( command_interface => $opts{command_interface} ); $obj->_set_fields( row => $row ); push @state_objs, $obj; } } catch NACL::APISet::Exceptions::InvalidParamValueException with { # Ignore the path, if it is invalid }; } $Log->exit(); return @state_objs; } ## end sub _fetch_7mode_cli # To support the multiple path with node-scope-node name sub _convert_path_and_get_node { $Log->enter(); my ( $pkg, %opts ) = @_; my $path = $opts{filter}{path}; # "path" could have quotes if there are special characters. Strip them out # since these are not applicable $path =~ s/"//g; # Value could be a pipe-separated list, so split on pipe. We have extra # considerations to make because the file-name itself could have pipes. # For example, a|b|c is a valid file-name. Simply splitting on pipe would # result in a, b and c being returned as separate files. Here's the # approach followed: # 1. Split on pipe. If there is only one entry after splitting, then # there's only one path provided. # 2. If there are multiple entries, then there could be more than one path. # Let's take "/aggr/aggrname/a|/aggr/aggrname/b|c|d" as example. # After splitting: # [ '/aggr/aggrname/a', '/aggr/aggrname/b', 'c', 'd' ] # The first entry has to be either the first path or part of the first path. # @new_paths = ( '/aggr/aggrname/a') # The second entry has to be a new path because it starts with a /, so # that becomes the next path: # @new_paths = ( '/aggr/aggrname/a', '/aggr/aggrname/b') # The next entry ("c") is a new path since it does not start with # a /, hence it must have been part of the previous path. So we concatenate # it with the previous path: # @new_paths = ( '/aggr/aggrname/a', '/aggr/aggrname/b', '/aggr/aggrname/c') # The next entry ("d") also has to be part of the previous path so is also # concatenated: # @new_paths = ( '/aggr/aggrname/a', '/aggr/aggrname/b', '/aggr/aggrname/c', '/aggr/aggrname/d') # So, to summarize: # 1. Split on pipe. # 2. If value starts with /, then it's a new path so create a new entry. # Else, it's part of the previous path, so concatenate it with the previous # path. my @paths = split /\|/, $path; my @new_paths; if ( @paths == 1 ) { @new_paths = @paths; } else { $new_paths[0] = shift @paths; my $j = 0; foreach my $path (@paths) { if ( $path =~ m!^/! ) { $new_paths[ ++$j ] = $path; } else { my $prev_path = $new_paths[$j]; my @aggr_path = split( /\//, $prev_path ); $new_paths[ ++$j ] = "/$aggr_path[1]/$aggr_path[2]/$path"; } } } # For all the path get the node-scope-name my %path_with_node_scope; if ($opts{command_interface}->is_cmode()) { if ( !$opts{'nodescope-node-name'} && $opts{determine_appropriate_nodescope_node} ) { my %common_options; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_options ); foreach $path (@new_paths) { # We may get the error for the invalid path, if so ignore try { my $node = NACL::C::AggregateFile->get_local_node_name( path => $path, %common_options ); if (exists $path_with_node_scope{$node}) { $path_with_node_scope{$node} .= " $path"; } else { $path_with_node_scope{$node} = $path; } } catch NACL::Exceptions::NoElementsFound with { # Ignore that path }; } %path_with_node_scope = reverse %path_with_node_scope; $Log->exit(); return %path_with_node_scope; } } my $full_path = join (" ", @new_paths); # if 7Mode or determine_appropriate_nodescope_node = 0 then no need of # node-scope-name $path_with_node_scope{$full_path} = $opts{'nodescope-node-name'} || undef; $Log->exit(); return %path_with_node_scope; } # Make determine_appropriate_nodescope_node and nodescope-node-name valid arguments sub _fetch_validate_spec { $Log->enter(); my ($pkg, @args) = @_; my %spec = ( %{$pkg->SUPER::_fetch_validate_spec(@args)}, determine_appropriate_nodescope_node => {type => SCALAR, default => 1}, 'nodescope-node-name' => {type => SCALAR, optional => 1}, ); $Log->exit(); return {%spec}; } # Make determine_appropriate_nodescope_node and nodescope-node-name valid arguments sub _fetch_backend_validate_spec { $Log->enter(); my ($pkg, @args) = @_; my %spec = ( %{$pkg->SUPER::_fetch_backend_validate_spec(@args)}, determine_appropriate_nodescope_node => {type => SCALAR, default => 1}, 'nodescope-node-name' => {type => SCALAR, optional => 1}, ); $Log->exit(); return {%spec}; } 1;