# # Copyright (c) 2001-2011 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary CIFS Task Module ## @author dl-nacl-dev@netapp.com, swati.bajaj@netapp.com ## @status shared ## @pod here =head1 NAME NACL::MTask::CIFS =head1 DESCRIPTION C provides a number of well-defined but potentially complex or multi-step methods related to CIFS in ONTAP. The MTask will mainly be concerned with setting up of Cifs server given the parameters that are given below. =head1 ATTRIBUTES =head2 export_policy Represents the NACL::C::ExportPolicy instance that was created during the setup =head2 cifs_server Represents the NACL::C::VserverCifs instance that was created during the setup =head2 unix_group Represents an array of NACL::STask::VserverServicesUnixGroup instances that was created during the setup =head2 unix_user Represents an array of NACL::STask::VserverServicesUnixUser instances that was created during the setup =head2 name_mapping Represents an array of NACL::C::VserverNameMapping instances that was created during the setup =head2 dns_service Represents the NACL::C::VserverServicesDns instance that was created during the setup =head2 << nacltask_replay_timeout => $val >> (Optional, SCALAR) The total timeout before complaining that the replay didn't succeed. In order for this wait not to happen, timeout can be specified as 0. The default value is 120 seconds. This is for waiting for the config change to be replayed to the nblade. This can be skipped by sending in the value as 0. =over =item C<< nacltask_replay_interval => $val2 >> (Optional, SCALAR) The interval between two invocations of diag nblade replay show. The default value is 5 seconds. Applicable only if the nacltask_replay_timeout value is greater than 0. =back =head2 setup # Specifying minimal options, and the library taking the other defaults, my $cifs_obj = NACL::MTask::CIFS->setup( 'command_interface' => $command_interface, 'vserver' => $vserver_name, 'if_exists' => $action, # default 'die' 'cifs-server' => $cifs_server, 'cifs_domain' => $cifs_domain_name, 'username' => $cifs_username, 'password' => $cifs_password, %other_options ); # Specifying all the various options, my $cifs_obj = NACL::MTask::CIFS->setup( 'command_interface' => $command_interface, 'vserver' => $vserver_name, 'if_exists' => $action, # default 'die' 'cifs-server' => $cifs_server, 'cifs_domain' => $cifs_domain_name, 'username' => $cifs_username, 'password' => $cifs_password, 'name-servers' => \@name_servers, 'policyname' => $policy_name, 'unix_groups' => [ { 'name' => $unix_group_name, 'id' => $id, }, ], 'unix_users' => [ { 'user' => $unix_user_name, 'id' => $id, 'primary-gid' => $gid, }, ], 'vserver_name_mapping' => [ { 'pattern' => $pattern, 'direction' => $direction, 'replacement' => $replacement, 'position' => $position, }, ], %other_options ); (Class method)This method should be called only for CMode CIFS setup. Given the vserver name, cifs server name, cifs domain name, username and password, this task configures cifs server on the provided command interface. This method provides additional services beyond what's inherent to the product's cifs creation commands, as controlled and described by the new "if_exists" option. In addition to creating the cifs server, it also takes care of the following dependencies. In case of CMode, it handles, 1) Adding the Cifs license if not added previously 2) Adding cifs protcol to the allowed-protccols list of the vserver, if not added previously 3) Creating the vserver export policy. 4) Creating the dns given the name-servers. 5) Creating the name-mapping, both unix-win and win-unix. 6) Creating different unix users 7) Creating different group users =over =item Options =over =item C<< if_exists=>$action >> (Optional) specifies an action to be taken if the cifs server already exists. $action - if set to "die"(default), an exception is raised. - if set to "reuse", no action is performed, and return the task object referring to the corresponding cifs server. - if set to "purge", deletes the existing cifs server before creating it. =item C<< vserver => $vserver_name >> (Required) The name of the vserver. =item C<< cifs_domain => $cifs_domain_name >> (Required) The domain name (NetBIOS Domain/Workgroup Name) for the CIFS server. =item C<< dns_domain => $dns_domain_name >> (Optional) The DNS domain name. When this is not given, cifs_domain will be considered as domain. =item C<< cifs-server => $cifs-server >> (Required) The name of the CIFS server to be created. =item C<< username => $username >> (Required) The username to be created in the corressponding CIFS server. =item C<< password => $password >> (Required) The password required to be set for the user. =item C<< name-servers => \@name_servers >> An array ref containing the list of name-servers. Required if the DNS server is not created for the particular cifs domain given. If the DNS server is created by the user, then this is an optional parameter. =item C<< unix_groups => $unix_groups >> (Optional) An array ref containing hashes with the group name and group id indicating the unix groups to be created See also L =item C<< unix_users => $unix_users >> (Optional) An array ref containing hashes with the unix user name,id and primary group id indicating the unix users to be created See also L =item C<< vserver_name_mapping => $vserver_name_mapping >> (Optional) An array ref containing hashes with the vserver name-mapping parameters See also L =back =item command_interface, apiset_must, apiset_should, method-timeout, all the other options supported by NACL::C::ExportPolicy->create, NACL::C::VserverServicesDns->create, NACL::C::VserverCifs->create are supported by NACL::MTask::CIFS->setup as well. =back =cut package NACL::MTask::CIFS; use strict; use warnings; use base qw(NACL::MTask::MTask NACL::STask::STask ); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use NACL::C::VserverCifs; use NACL::C::VserverNameMapping; use NACL::C::VserverServicesDns; use NACL::STask::VserverServicesDns; use NACL::STask::VserverServicesUnixGroup; use NACL::STask::VserverServicesUnixUser; use NACL::C::VserverServicesNameServiceGetxxbyyy; use NACL::C::ExportPolicy; use NACL::C::License; use NACL::STask::STask; use NACL::C::Component; use NACL::C::Options; use NACL::CS::Options; use NACL::STask::SystemLicense; use NACL::STask::Vserver; use NACL::STask::Node; use NACL::STask::VserverCifsOptions; use NATE::Exceptions::Argument qw(:try); use NACL::APISet::Exceptions::ResponseException; use NACL::APISet::Exceptions::CommandFailedException qw(:try); use Params::Validate qw/validate_with SCALAR ARRAYREF/; use NACL::InstantComponent qw(instant_component); use NATE::ParamSet qw(param_global); ############################################################################################################ # Name : setup # Description : Actual task function to be invoked by test scripts. POD above has complete details # Creates export policies, DNS servers, name-mappings, Unix Users, Unix Groups, # and then creates CIFS server. ############################################################################################################ sub setup { $Log->enter() if $may_enter; my $pkg = shift; NATE::BaseException->throw( "Setup can only be invoked with a proper package call") if ref $pkg; my $obj; my %opts = validate_with( params => \@_, spec => { %{ NACL::C::Component->_common_validate_spec() }, if_exists => $pkg->_if_exists_validate_spec(), vserver => { type => SCALAR }, policyname => { type => SCALAR, default => 'default' }, cifs_domain => { type => SCALAR , optional => 1 }, dns_domain => { type => SCALAR, optional => 1 }, vserver_name_mapping => { type => ARRAYREF, default => [ { 'direction' => 'unix-win', 'pattern' => 'root', #'position' => '', # This will be filled up later after validation #'replacement' => '', # This will be filled up later after validation }, ] }, unix_users => { type => ARRAYREF, default => [ { 'user' => 'root', 'id' => '0', 'primary-gid' => '0', }, ] }, unix_groups => { type => ARRAYREF, default => [ { 'name' => 'root', 'id' => '0', }, ] }, 'cifs-server' => { type => SCALAR, optional => 1 }, username => { type => SCALAR , optional => 1}, password => { type => SCALAR, optional => 1 }, nacltask_replay_timeout => { type => SCALAR, default => 120 }, nacltask_replay_interval => { type => SCALAR, default => 5 } }, allow_extra => 1, ); my $nacltask_options = {}; $pkg->_move_nacltask_options( source => \%opts, target => $nacltask_options ); ## CIFS_USERNAME,CIFS_PASSWORD,CIFS_DOMAIN,CIFS_SERVER param are optionsla and accepted via CMD or scripts _populate_setup_params_from_cmd(opts => \%opts); my %common_args; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_args, ); # extract necessary details from the object my $command_interface = $opts{command_interface}; my $vserver = $opts{vserver}; # To have a backward compatibility, when dns_domin is not provided, consider cifs_domain. my @domain_list = ( $opts{dns_domain} ? $opts{dns_domain} : $opts{cifs_domain} ); # @domain_list will only have one element. if its comma separated make it array. @domain_list = split(',',$domain_list[0]); my @name_mapping = @{ $opts{vserver_name_mapping} }; NATE::BaseException->throw("This method should be called only for CMode") if $command_interface->mode() ne 'CMode'; # Filling the defaults for 'replacement', 'position' and 'direction' for # 'vserver_name_mapping' parameter # This is not done during validation because it requires value of another option # to fill the defaults my $position = 1; for ( my $i = 0; $i < scalar @name_mapping; $i++ ) { unless ( defined( $opts{'vserver_name_mapping'}[$i]{'replacement'} ) ) { my $domain = $opts{cifs_domain}; $domain =~ s/(\w+)(\.\w+)*/$1/e; $opts{'vserver_name_mapping'}[$i]{'replacement'} = $domain . '\\\\administrator'; } unless ( defined( $name_mapping[$i]{'position'} ) ) { $name_mapping[$i]{'position'} = $position++; } unless ( defined( $name_mapping[$i]{'direction'} ) ) { $name_mapping[$i]{'direction'} = 'unix-win'; } } my %component_call_opts = %opts; my $change = 0; # If policy name is 'default', then purge does not work. 'default' # export policy once created, cannot be deleted. # To avoid failures unnecessarily, we change if_exists to 'reuse' # only in this case if ( $opts{if_exists} eq 'purge' && $opts{'policyname'} eq 'default' ) { $component_call_opts{if_exists} = 'reuse'; $change = 1; } # Create export-policy given the policy name $obj->{export_policy} = _handle_creation( %component_call_opts, component => 'NACL::C::ExportPolicy', method => 'create', delete_method => 'delete', ); if ($change) { $component_call_opts{if_exists} = $opts{if_exists}; } # Create DNS if it does not exist. This is a pre-requisite # for CIFS creation. If the 'name-servers' are not provided, # and the DNS corresponding to the domain does not exist, # an exception is thrown. if ( $opts{'name-servers'} ) { $component_call_opts{domains} = \@domain_list; $component_call_opts{state} = 'enabled'; $obj->{dns_services} = _handle_creation( %component_call_opts, component => 'NACL::STask::VserverServicesDns', method => 'create', delete_method => 'delete', nacltask_wait => 1 ); } else { # Lets try finding if the dns with the given domain name already exist. $obj->{dns_service} = NACL::C::VserverServicesDns->find( command_interface => $command_interface, filter => { vserver => $vserver, domains => \@domain_list, }, ); } # create vserver name mapping 'unix-win' and 'win-unix' for ( my $i = 0; $i < scalar @name_mapping; $i++ ) { $component_call_opts{pattern} = $name_mapping[$i]{pattern}; $component_call_opts{replacement} = $name_mapping[$i]{replacement}; $component_call_opts{direction} = $name_mapping[$i]{direction}; $component_call_opts{position} = $name_mapping[$i]{position}; push( @{ $obj->{name_mapping} }, _handle_creation( %component_call_opts, component => 'NACL::C::VserverNameMapping', method => 'insert', delete_method => 'delete', ) ); # The pattern, replacement and direction is the reverse of # the previous name-mapping. The position could be the same $component_call_opts{pattern} = $name_mapping[$i]{replacement}; $component_call_opts{replacement} = $name_mapping[$i]{pattern}; $component_call_opts{direction} = $name_mapping[$i]{direction} eq 'unix-win' ? 'win-unix' : 'unix-win'; push( @{ $obj->{name_mapping} }, _handle_creation( %component_call_opts, component => 'NACL::C::VserverNameMapping', method => 'insert', delete_method => 'delete', ) ); } # Create Unix Groups my @unix_groups = @{ $opts{unix_groups} }; for ( my $i = 0; $i < scalar @unix_groups; $i++ ) { push( @{ $obj->{unix_group} }, NACL::STask::VserverServicesUnixGroup->create( %common_args, %{ $unix_groups[$i] }, 'vserver' => $vserver, 'nacltask_if_exists' => 'purge', 'purge_if_duplicate_by_id' => 1, nacltask_wait => 1 ) ); } #Create Unix Users my @unix_users = @{ $opts{unix_users} }; for ( my $i = 0; $i < scalar @unix_users; $i++ ) { push( @{ $obj->{unix_user} }, NACL::STask::VserverServicesUnixUser->create( %common_args, %{ $unix_users[$i] }, 'vserver' => $vserver, 'nacltask_if_exists' => 'purge', 'purge_if_duplicate_by_id' => 1, nacltask_wait => 1 ) ); } # At this point hopefully all the pre-requisites for cifs creation # is handled. Creating the CIFS server $component_call_opts{domain} = $opts{'cifs_domain'}; if (defined $component_call_opts{workgroup}){ if (defined $component_call_opts{domain}) { delete $component_call_opts{domain}; } } LOOP: { try { $obj->{cifs_server} = _handle_creation( %component_call_opts, component => 'NACL::C::VserverCifs', method => 'create', delete_method => 'delete', ); } catch NACL::APISet::Exceptions::ResponseException with { my $err_msg = shift; if ( $err_msg->text() =~ /You do not have a valid CIFS protocol license/ || $err_msg->text() =~ /You do not have a valid license for \"CIFS\"/ || $err_msg->text() =~ /The protocol cifs is not allowed to run on / ) { # Add the CIFS license if not added previously NACL::STask::SystemLicense->add( command_interface => $command_interface, package => 'cifs' ); NACL::STask::Vserver->add_to_array_attribute( command_interface => $command_interface, 'attribute' => 'allowed-protocols', 'list' => [qw ( cifs )], vserver => $component_call_opts{vserver}, ); goto LOOP; } else { $Log->exit() if $may_exit; $err_msg->throw(); } }; } $Log->exit() if $may_exit; bless $obj, $pkg; return $obj; } ## end sub setup =head2 setup_7mode my $cifs = NACL::MTask::CIFS->setup_7Mode( 'command_interface' => $Node, 'cifs-server' => $Cifs_server, 'domain-user' => $User, 'domain-pass' => $Password, 'auth-type' => "", workgroup => "WWW", if_exists => "purge"); (Class method)This method should be called only for 7Mode Cifs setup. Given a command interface , cifs server name, cifs domain name,username and password, this task configures cifs server. This method provides additional services beyond what's inherent to the product's cifs creation commands, as controlled and described by the new "if_exists" option. It also takes care of the following dependencies. 1) Adding the Cifs license if not added previously 2) Sets security.passwd.rules.enable to off, if not set 3) Sets security.passwd.rules.everyone to off, if not set Inaddition the if_exist option it also takes all the input parameters of the setup method in VserverCifs.pm See L =over =item Options =over =item C<< if_exists=>$action >> (Optional) specifies an action to be taken if the cifs server already exists. $action - if set to "die"(default), an exception is raised. - if set to "reuse", no action is performed, and return the task object referring to the corresponding cifs server. - if set to "purge", deletes the existing cifs server before creating it. =back =back =cut sub setup_7Mode { $Log->enter() if $may_enter; my ( $existing, $ret, $do_not_terminate ); my $pkg = shift; NATE::BaseException->throw( "Setup can only be invoked with a proper package call") if ref $pkg; my %opts = validate_with( params => \@_, spec => { %{ NACL::C::Component->_common_validate_spec() }, if_exists => $pkg->_if_exists_validate_spec(), }, allow_extra => 1, ); my $command_interface = $opts{command_interface}; my $if_exists = delete $opts{if_exists}; NATE::BaseException->throw( "This method should be called only for 7Mode cifs setup") if ( $command_interface->mode() ne '7Mode' ); my %common_args; $pkg->_copy_common_component_params_with_ci( source => \%opts, target => \%common_args, ); NACL::STask::Node->set_current_datetime(%common_args); foreach my $option ( qw(security.passwd.rules.enable security.passwd.rules.everyone)) { NACL::C::Options->option( command_interface => $command_interface, 'option-name' => $option, 'option-value' => 'off' ); } LOOP: try { $ret = NACL::C::VserverCifs->setup(%opts); } catch NACL::APISet::Exceptions::CommandFailedException with { my $err_msg = shift; if ( $err_msg->text() =~ /CIFS is active/ ) { if ( $if_exists eq 'die' ) { $Log->exit() if $may_exit; $err_msg->throw(); } elsif ( $if_exists eq 'reuse' ) { $ret = NACL::C::VserverCifs->new( command_interface => $command_interface ); } else { NACL::C::VserverCifs->delete( command_interface => $command_interface ); goto LOOP; } } elsif ( $err_msg->text() =~ /not licensed/ ) { NACL::STask::SystemLicense->add( command_interface => $command_interface, feature => 'cifs' ); no warnings qw(exiting); goto LOOP; } else { $Log->exit() if $may_exit; $err_msg->throw(); } }; $Log->exit() if $may_exit; return $ret; } ## end sub setup_7Mode ############################################################################################################ # Name : _handle_creation # Description : Invoked from setup. If there are duplicate entries already existing on the filer, based on # if_exists option which might be to die,purge or reuse, appropriate action is taken. If e.g. # if_exists => purge, It will call purge action which deletes the entries and recreates # the entries. If that configuration is not there, it simply creates the entry. Returns an # object of the requested component type only if this method creates one so that this can be # stored as an attribute for deletion during cleanup. ############################################################################################################ sub _handle_creation { $Log->enter() if $may_enter; #my %opts = $pkg_or_obj->_common_validate_with( # params => \@_, # allow_extra => 1, #); my %opts = @_; # get the name of the component and the name of the method required for # create/add my $pkg = $opts{component}; my $method = $opts{method}; # extract other necessary details my $command_interface = $opts{command_interface}; my $if_exists = $opts{if_exists}; my $existing = undef; # We will not handle the if_exists => 'die' case here since the # create call will anyway throw an error if the element exists. if ( $if_exists ne 'die' ) { $Log->trace('Attempt to find the element'); $existing = $pkg->find( command_interface => $command_interface, filter => $pkg->get_primary_keys_options(%opts), allow_empty => 1 ); } if ($existing) { $Log->trace('Found a matching element'); if ( $if_exists eq 'purge' ) { _purge(%opts); } elsif ( $if_exists eq 'reuse' ) { $Log->exit() if $may_exit; return $existing; } } # Burt 1005088: sleep is required to prevent DNS lookup failures. Tharn::snooze(5) if ( $pkg =~ /VserverServicesDns/i && $if_exists eq 'purge' ); # Create the element here if not found above, and if if_exists is set to 'die' my $self = $pkg->$method( %opts, command_interface => $command_interface, allow_extra => 1 ); my $is_smb1_enb_present = $command_interface->apiset()->is_option_present( api=>'vserver_cifs_options_modify', field =>'smb1-enabled' ); if($method eq 'create' && $pkg eq 'NACL::C::VserverCifs' && $is_smb1_enb_present == 1 ){ #as per burt 1093491 adding cifs option modify command to handle the new feature. NACL::STask::VserverCifsOptions->modify( command_interface => $command_interface, 'vserver' => $opts{vserver}, 'smb1-enabled' => 'true', ) } $Log->exit() if $may_exit; return $self; } ## end sub _handle_creation ############################################################################################################ # Name : _purge # Description : Invoked from _handle_creation. It takes the appropriate delete method to be used and the # component that has requested for purge, frames the method with appropriate parameters # to be called and invokes the same. ############################################################################################################ sub _purge { $Log->enter() if $may_enter; my %opts = @_; my $pkg = $opts{component}; my $method = $opts{delete_method}; my $command_interface = $opts{command_interface}; # call either delete/remove on the package provided $pkg->$method( command_interface => $command_interface, %opts, allow_extra => 1 ); $Log->exit() if $may_exit; } ## end sub _purge ############################################################################################################ # Name : _populate_setup_params_from_cmd # Description : Invoked from setup. It updates the following parameter to get updated # from hostrec or CMD. ############################################################################################################ sub _populate_setup_params_from_cmd { $Log->enter() if $may_enter; my %args = @_; my $opts = $args{opts}; if ( !(defined $opts->{workgroup} ) ) { $opts->{username} //= param_global->get('CIFS_USERNAME') ; $opts->{password} //= param_global->get('CIFS_PASSWORD') ; } $opts->{cifs_domain} //= param_global->get('CIFS_DOMAIN') ; $opts->{'cifs-server'} //= param_global->get('CIFS_SERVER') ; $opts->{'dns_domain'} //= param_global->get('FILER_DNS_DOMAINNAME') ; $Log->exit() if $may_exit; } ## end sub _purge 1;