# # Copyright (c) 2001-2013 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary VolumeFile ComponentState Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::CS::VolumeFile =head1 DESCRIPTION C is a derived class of L. It represents the state of an ONTAP VolumeFile. A related class is L, which represents access to an ONTAP VolumeFile. Note that when $volume_dir_obj->state(); is invoked a C object is returned. =head1 ATTRIBUTES Each field of output of the C ZAPI are the attributes of the VolumeFile 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 >> Name of the file. For example, if the file path is "/vol/vol0/file1", the name would be "file1". Filled in for CMode and 7Mode ZAPI. =item C<< file_type >> Type of the file. Possible values: file, directory, blockdev, chardev, symlink, socket, fifo, stream, lun. Filled in for CMode and 7Mode ZAPI. =item C<< acl_type >> The type of access control list (acl) on the file. Possible values are: "no_acl", "nt_acl", "nfs_acl", and goddess forbid, "unknown". Filled in for CMode and 7Mode ZAPI. =item C<< inode_number >> The file inode number. Filled in for CMode and 7Mode ZAPI. =item C<< file_size >> The size of the file in bytes. Value is in the range of 0..(2^63)-1 Filled in for CMode and 7Mode ZAPI. =item C<< accessed_timestamp >> Last access time of the file. The value is in seconds since January 1, 1970. Filled in for CMode and 7Mode ZAPI. =item C<< creation_timestamp >> Creation time of the file. The value is in seconds since January 1, 1970. Filled in for CMode and 7Mode ZAPI. =item C<< changed_timestamp >> Last changed time of the file. The value is in seconds since January 1, 1970. Filled in for CMode and 7Mode ZAPI. =item C<< path >> The full path to the file. Filled in for CMode and 7Mode ZAPI. =item C<< group_id >> The integer id of the group owner of the file. Filled in for CMode and 7Mode ZAPI. =item C<< bytes_used >> The number of bytes actually used on disk by this file. Filled in for CMode and 7Mode ZAPI. =item C<< hard_links_count >> The number of hard links to the file. Filled in for CMode and 7Mode ZAPI. =item C<< modified_timestamp >> Last modification time of the file. The value is in seconds since January 1, 1970. Filled in for CMode and 7Mode ZAPI. =item C<< is_vm_aligned >> Returns true if the file is vm-aligned. A vm-aligned file is a file which is padded with zero-filled data at the beginning so that it's actual data starts at a different offset instead of zero. This is done in VM environments so that reads/writes to this file are aligned to WAFL's 4k block boundary. The amount by which the start offset is adjusted depends on the vm-align setting of the hosting volume. Use L to get this information (see the vm-align-* attributes). Filled in for CMode and 7Mode ZAPI. =item C<< owner_id >> The integer id of the owner of the file. Filled in for CMode and 7Mode ZAPI. =item C<< perm >> File permission bits. It's similar to Unix style permission bits: 0755 gives read/write/execute permissions to owner and read/execute to group and other users. It consists of 4 octal digits derived by adding up bits 4, 2 and 1. Omitted digits are assumed to be zeros. First digit selects the set user ID(4), set group ID (2) and sticky (1) attributes. The second digit selects permission for the owner of the file: read (4), write (2) and execute (1); the third selects permissions for other users in the same group; the fourth for other users not in the group. Filled in for CMode and 7Mode ZAPI. =item C<< vserver >> The vserver through which the volume hosting the file is accessible. Filled in for CMode. =item C<< is_junction >> Determines whether file/dir in a volume is part of nested junction path or not. Filled in for CMode. =item C<< directory_path >> The path to the directory holding the file. For example, if the file's path is "/vol/myvol/mydir/myfile", then the directory_path would be "/vol/myvol/mydir". =item C<< msid >> =item C<< dsid >> =item C<< inode_gen_number >> =back =cut package NACL::CS::VolumeFile; use strict; use warnings; use Params::Validate qw(:all); use NACL::ComponentUtils qw(_dump_one); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use Data::Dumper; use NACL::Exceptions::NoElementsFound qw(:try); use NATE::Exceptions::Argument; use NACL::C::VolumeDirectory; use NACL::C::_Mixins::VolumeFileDirectory qw(:all); use base 'NACL::CS::ComponentState::ONTAP'; use Class::MethodMaker [ scalar => [ { # "name" might not be present in the returned ZAPI output. -default_ctor => sub { $_[0]->_get_name_from_path } }, 'name', ], scalar => 'file_type', scalar => 'acl_type', scalar => 'inode_number', scalar => 'file_size', scalar => 'accessed_timestamp', scalar => 'creation_timestamp', scalar => 'changed_timestamp', scalar => 'path', scalar => 'group_id', scalar => 'bytes_used', scalar => 'hard_links_count', scalar => 'modified_timestamp', scalar => 'is_vm_aligned', scalar => 'owner_id', scalar => 'perm', scalar => 'vserver', scalar => 'is_junction', scalar => 'msid', scalar => 'dsid', scalar => 'inode_gen_number', scalar => [ { -default_ctor => sub { $_[0]->_determine_directory_path(); } }, 'directory_path' ] ]; =head1 METHODS =head2 fetch # Scalar context, 7Mode my $VolumeFile_state = NACL::CS::VolumeFile->fetch( command_interface => $ci, filter => { path => $file_path } ); # List context, 7Mode my @VolumeFile_states = NACL::CS::VolumeFile->fetch( command_interface => $ci, filter => { path => $file_path } ); # Scalar context, CMode my $VolumeFile_state = NACL::CS::VolumeFile->fetch( command_interface => $ci, filter => { path => $file_path, vserver => $vserver } ); # List context, CMode my @VolumeFile_states = NACL::CS::VolumeFile->fetch( command_interface => $ci, filter => { path => $file_path, vserver => $vserver } ); # Explicitly specify to *not* handle name-ordinal my @VolumeFile_states = NACL::CS::VolumeFile->fetch( command_interface => $ci, handle_name_ordinal => 0, filter => { path => $file_path, vserver => $vserver } ); (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. Also, for CMode, C is mandatory in the filter. Uses a CMode or 7Mode ZAPI APISet. =over =item Options In addition to the options accepted by all other fetch methods, the C method accepts an additional C argument. See L for a description of name-ordinal and the functionality provided by the C argument. =back =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() if $may_enter; my ($pkg, @args) = @_; my %opts = validate (@args, $pkg->_fetch_validate_spec()); my $path = $opts{filter}{path}; unless (defined $path) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "$pkg::fetch: 'path' has to be provided in the filter for " . 'the call to work'); } my $paths = $pkg->_convert_path( path => $path, handle_name_ordinal => $opts{handle_name_ordinal}, ); my @state_objs = $pkg->SUPER::fetch( %opts, # Split into paths here in the front-end. The back-end accepts this # list as "_paths" and uses this (does not need to look at the filter) _paths => $paths, choices => [ { method => '_fetch_zapi', interface => 'ZAPI', set => 'CMode|7Mode', }, ], exception_text => 'No matching volume file(s) found', frontend => 'NACL::CS::VolumeFile::fetch', ); $Log->exit() if $may_exit; return wantarray ? @state_objs : $state_objs[0]; } ## end sub fetch sub _fetch_zapi { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my %opts = validate @args, $pkg->_fetch_backend_validate_spec(); my $apiset = $opts{apiset}; my $command_interface = $opts{command_interface}; my $paths = $opts{_paths}; my $is_cmode = $command_interface->is_cmode(); my $vserver; my %api_args; if ($is_cmode) { $vserver = $pkg->_handle_zapi_vserver_context( api_opts => \%api_args, vserver => $opts{filter}->{vserver}, command_interface => $command_interface, frontend_method => 'fetch', ); } my @state_objs; foreach my $path (@$paths) { try { my $response = $apiset->file_get_file_info(path => $path, %api_args); my $only_row = $response->get_parsed_output()->[0]{'file-info'}[0]; my $obj = $pkg->new(command_interface => $command_interface); $obj->_set_fields(row => $only_row, add_unknown_fields => 1); $obj->vserver($vserver) if $is_cmode; # The ZAPI returns the name of the file, not the full path, so get this # from the input. $obj->path($path); push @state_objs, $obj; } catch NACL::APISet::Exceptions::ResponseException with { my $exception = $_[0]; if ($exception->error_number() == 2) { # This error number indicates file/directory doesn't exist } else { $Log->exit() if $may_exit; $exception->throw(); } }; } $Log->exit() if $may_exit; return @state_objs; } sub get_C_package_name { $Log->enter() if $may_enter; my $self = $_[0]; my $c; # If invoked with an object, "file_type" is set, and is "directory" then # the relevant component is NACL::C::VolumeDirectory. Else it is # NACL::C::VolumeFile if (ref $self) { my $type = $self->file_type(); if (defined $type && $type eq 'directory') { $c = 'NACL::C::VolumeDirectory'; } } $c = 'NACL::C::VolumeFile' unless defined $c; $Log->exit($c); return $c; } # The fields in the object which are populated despite not showing up # in the ZAPI output. (Obtained either from input or by processing the value # of some other field) sub _extra_populated_fields { $Log->enter() if $may_enter; my @arr = (qw(vserver directory-path path)); $Log->exit() if $may_exit; return wantarray ? @arr : [@arr]; } sub _get_name_from_path { $Log->enter() if $may_enter; my $self = $_[0]; return unless ref $self; my $name = $self->get_file_or_dir_name_from_path(path => $self->path()); $Log->exit() if $may_exit; return $name; } # Make handle_name_ordinal, _paths valid arguments sub _fetch_validate_spec { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my %spec = ( %{$pkg->SUPER::_fetch_validate_spec(@args)}, handle_name_ordinal => {type => SCALAR, default => 1}, # This would not be passed in by callers, but this gets populated # by the fetch front-end method, so when the base-class calls # _fetch_validate_spec, it would require "_paths" to be understood # as valid. _paths => {type => ARRAYREF, optional => 1}, # Back-end dynamically adds fields not present in MethodMaker unknown_fields_added_by_backend => {type => SCALAR, default => 1}, ); $Log->exit() if $may_exit; return {%spec}; } # Make handle_name_ordinal a valid argument and make _paths mandatory sub _fetch_backend_validate_spec { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my %spec = ( %{$pkg->SUPER::_fetch_backend_validate_spec(@args)}, handle_name_ordinal => {type => SCALAR, default => 1}, _paths => { type => ARRAYREF }, ); $Log->exit() if $may_exit; return {%spec}; } # Provided a path filter value, convert it into a list of paths. # This involves removing the name-ordinal; removing quotes; splitting on pipe sub _convert_path { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my %opts = validate_with( params => \@args, spec => { path => { type => SCALAR }, handle_name_ordinal => {type => SCALAR, default => 1}, } ); my $path = $opts{path}; # "path" could have quotes if there are special characters. Strip them out # since these are not applicable for ZAPI $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 "/vol/volname/a|/vol/volname/b|c|d" as example. # After splitting: # [ '/vol/volname/a', '/vol/volname/b', 'c', 'd' ] # The first entry has to be either the first path or part of the first path. # @new_paths = ( '/vol/volname/a') # The second entry has to be a new path because it starts with a /, so # that becomes the next path: # @new_paths = ( '/vol/volname/a', '/vol/volname/b') # The next entry ("c") cannot be 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 = ( '/vol/volname/a', '/vol/volname/b|c') # The next entry ("d") also has to be part of the previous path so is also # concatenated: # @new_paths = ( '/vol/volname/a', '/vol/volname/b|c|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 { $new_paths[$j] .= "|$path"; } } } if ($opts{handle_name_ordinal}) { @new_paths = map { $pkg->_remove_name_ordinal(path => $_) } @new_paths; } $Log->exit() if $may_exit; return \@new_paths; } # Handle paths with name-ordinals. The path stored in the CS object # will not have the name-ordinal, so strip out the name-ordinal from the # path and then filter. sub _apply_filter { $Log->enter() if $may_enter; my ($pkg, @args) = @_; my %opts = validate_with( params => \@args, spec => {filter => {type => HASHREF}}, allow_extra => 1 ); my %filter = %{$opts{filter}}; my $path = delete $filter{path}; $pkg->SUPER::_apply_filter(%opts, filter => \%filter); if (defined $path) { my $state_objs = $opts{state_objs}; my $paths = $pkg->_convert_path(path => $path); my %paths_hash = map { $_ => 1 } @$paths; my $matches_path = sub { # Check if the path of this state obj matches any of the # paths provided my ($state_obj) = @_; my $obj_path = $state_obj->path(); return exists $paths_hash{$obj_path}; }; @{$state_objs} = grep ($matches_path->($_), @{$state_objs}); } $Log->exit() if $may_exit; } 1;