# # Copyright (c) 2013-2017 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary ClusterPeer Task Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here package NACL::STask::ClusterPeer; use strict; use warnings; use base qw(NACL::C::ClusterPeer 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 Params::Validate qw(validate validate_with :types); use NACL::Exceptions::NoElementsFound qw(:try); use NATE::Exceptions::Argument (); use NATE::BaseException; use NACL::STask::NetworkInterface; use NACL::CS::ClusterPeer; use NACL::C::Node; use NATE::ParamSet qw(param_global); my $param_global = param_global(); =head1 NAME NACL::STask::ClusterPeer =head1 DESCRIPTION C provide methods to create & delete cluster peer relation-ship in OnTap. It is created on top of C component. Since it is derived class of C, we can use all the methods of C from the object of this task. =head1 ATTRIBUTES =head2 remote_cluster_peer This attribute contains the objects which is populated after the cluster peer relationship is created. This will contain the peer relationship information of the remote cluster. =head2 cluster The name of the remote cluster in the cluster peer relationship. =head2 command_interface This is type of object containing the command interface of the local cluster. =cut use Class::MethodMaker [scalar => "remote_cluster_peer",]; =head1 EXCEPTIONS 1. NACL::C::Exceptions::ClusterPeer::AlreadyExists (This type of exception is thrown when an attempt is made to create cluster peer relation that already exists). 2. NATE::Exceptions::VerifyFailure (This type of exception is thrown when verification fails for the created cluster peer relation. This type of exception is also thrown when verification fails for the deleted cluster peer relation). 3. NACL::C::Exceptions::ClusterPeer::DoesNotExist (This type of exception is thrown when an attempt is made to delete cluster peer relation that does not exists). 4. NACL::Exceptions::Timeout (This type of exception is thrown when cluster peer relationship is not in "Available" state even after the limit specified in method-timeout. This is applicable only if nacltask_wait option is enabled). =head1 METHODS =head2 create # Simple example of authenticated peer creation my $peer_obj = NACL::STask::ClusterPeer->create( command_interface => $local_ci, remote_interface => $remote_ci, create_options_home_cluster => { 'peer-addrs' => \@remote_addrs }, create_options_remote_cluster => { 'peer-addrs' => \@local_addrs }, passphrase => $phrase, nacltask_if_exists => "reuse", nacltask_wait => 1, nacltask_to_cleanup => 1, ); Creates an authenticated cluster peer relationship. The options specified in C are used only for the command issued on the local cluster, while the options specified in C are used only for the command issued on the remote cluster. All other options are used on both clusters. In the above example, it would mean that the passphrase value is used on both the local and remote clusters. # Example of authenticated peer creation without specifying passphrase my $peer_obj = NACL::STask::ClusterPeer->create( command_interface => $local_ci, remote_interface => $remote_ci, create_options_home_cluster => { 'peer-addrs' => \@remote_addrs }, create_options_remote_cluster => { 'peer-addrs' => \@local_addrs }, nacltask_if_exists => "reuse", nacltask_wait => 1, nacltask_to_cleanup => 1, ); Same as above, but without the passphrase specified. The task automatically generates a value (by using the random_name_generator() in STask.pm). # Reciprocal form of the call my $peer_obj = NACL::STask::ClusterPeer->create( command_interface => $local_ci, create_options_home_cluster => { 'peer-addrs' => \@remote_addrs }, username => $remote_username, password => $remote_password, nacltask_if_exists => "reuse", nacltask_wait => 1, nacltask_to_cleanup => 1, ); The "reciprocal" form of the call involves specifying the username and password of the remote cluster. In this form, the "create" command is only issued on the local cluster, so the C ,C and C options are not applicable. (Class method) This method is used to create the cluster-peer for the cluster. This method will also wait for the peer to be in "Available" state, by providing nacltask_wait as 1. Cleanup manager is enabled for this method. To enable the cleanup, nacltask_to_cleanup needs to be passed as 1 or nacltask_cleanup_manager needs to be passed. =over =item Options =over =item C<< command_interface => $ci >> (Required) The command_interface of the home cluster on which the cluster peer needs to created. =item C<< remote_interface => $remote_ci >> (Optional) The command interface of the remote cluster to which the peer relationship is to be created. This parameter is optional if username & password is passed, otherwise its a mandatory field. =item C<< username => $username >> (Optional) The username of the remote cluster. If this option is passed, no need of specifying the "create_options_remote_cluster" option since it is not required to run the command "cluster peer create" on the remote cluster to complete the peer relationship. This option is used when the reciprocal form of the call is made. =item C<< password => $password >> (Optional) The password of the remote cluster. If this option is passed, no need of specifying the "create_options_remote_cluster" option since it is not required to run the command "cluster peer create" on the remote cluster to complete the peer relationship. This option is used when the reciprocal form of the call is made. This option should be used together with the "username" option. =item C<< "passphrase" => $phrase >> (Optional) This option contains the value of the passphrase. Please take a look at "Arguments defaulted/auto-determined" section for more information. =item C<< create_options_home_cluster => \%opts >> (Required) This parameter accepts a hash-reference which contains the parameters required to create a cluster peer relationship on home cluster. The parameters can be viewed in L<< NACL::C::ClusterPeer->create|lib-NACL-C-ClusterPeer-pm/create >> method. eg : create_options_home_cluster => { 'peer-addrs' => \@peer_address_for_home_cluster } =item C<< create_options_remote_cluster => \%opts >> (Optional) This parameter accepts a hash-reference which contains the parameters required to create a cluster peer relationship on remote cluster. The parameters can be viewed in L<< NACL::C::ClusterPeer->create|lib-NACL-C-ClusterPeer-pm/create >> method. eg : create_options_remote_cluster => { 'peer-addrs' => \@peer_address_for_remote_cluster } This parameter is mandatory, if the "username" and "password" is not passed in the call to this method. =item C<< nacltask_if_exists => $action >> (Optional, defaults to "die") This option handles the case when the cluster peer is already done for the node. If the value is "die", C is thrown. If the value is "reuse", the existing cluster-peer relationship is used. If the value is "purge", the existing cluster-peer relationship is deleted, and the cluster-peer is re-created with the options passed. =item C<< nacltask_wait => 1|0 >> (Optional, Defaults to 0) This option is used to specify to wait till the state of the cluster peer relationship becomes "Available". By default, it waits for a maximum of 8 minutes (this can be configured using C). If it does not reach the Available state within this time, C is thrown. =item C<< _was_created => \$scalar >> (Optional) When this option is provided a reference to a scalar variable, the variable gets filled in with a boolean value describing whether the cluster peer was found (value will be 0; this scenario is possible when nacltask_if_exists => "reuse") or whether the cluster peer relationship was created (value will be 1). This is necessary to determine whether the cluster peer needs to be cleaned up later. my $was_created; my $peer_obj = NACL::STask::ClusterPeer->create( nacltask_if_exists => 'reuse', _was_created => \$was_created, nacltask_to_cleanup => 1, %other_opts ); # Operate on $peer_obj here # ... # Now determine whether to clean up, since we're not sure # whether we reused an existing cluster peer or created a new one if ($was_created) { # New cluster peer was created. Clean it up. $peer_obj->purge(); } =item C<< nacltask_to_cleanup => 0|1 >> (Optional, defaults to 0(no to cleanup)) Flag indicating if this operation is to be cleaned up or not. =item C<< nacltask_cleanup_manager >> Cleanup manager to be used for registering. Default : Will use the default cleanup manager. =item C<< "method-timeout" => $timeout >> (Optional) As component method-timeout, controls how long the command will wait before completing. However, the default timeout has been raised to 480 seconds. =item Other options All of the other various options accepted by L<< NACL::C::ClusterPeer->create|lib-NACL-C-ClusterPeer-pm/create >>. =back =back =over =item Arguments defaulted/auto-determined =over =item C This parameter is enabled only if the STask in run on FS build. Note that if this value is not specified, a default passphrase is assigned automatically using L. This parameter should not be used along with username/password options. If we want to prevent the task from generating a default value, then undef needs to be passed as the value for this option. my $peer_obj = NACL::STask::ClusterPeer->create( command_interface => $local_ci, remote_interface => $remote_ci, create_options_home_cluster => { 'peer-addrs' => \@remote_addrs }, create_options_remote_cluster => { 'peer-addrs' => \@local_addrs }, passphrase => undef, ); =back =back =over =item Exceptions =over =item C This type of exception is thrown when an attempt is made to create cluster peer relation that already exists. =item C This type of exception is thrown when cluster peer relationship is not in "Available" state even after the limit specified in method-timeout. This is applicable only if nacltask_wait option is enabled. =item IS_SIMPLICITY_ENABLED (default 0) Global flags that affect the behavior of create command. This flag will make the library to generate passphrase using command "cluster peer create -generate-passphrase" on remote cluster to enable cluster peering using simplicity method, while creating cluster peer on local cluster same passphrase will be used to support cluster peer simplicity. =back =back =cut sub create { my ($pkg, @args) = @_; my %opts = $pkg->_common_validate_with( params => \@args, additional_spec => { remote_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface', optional => 1 }, username => {type => SCALAR, optional => 1}, password => {type => SCALAR, optional => 1}, passphrase => {type => SCALAR | UNDEF, optional => 1}, create_options_home_cluster => {type => HASHREF}, create_options_remote_cluster => {type => HASHREF, optional => 1}, nacltask_if_exists => $pkg->_if_exists_validate_spec(), nacltask_wait => {type => BOOLEAN, default => 0}, "method-timeout" => {type => SCALAR, default => 480}, _was_created => {type => SCALARREF, optional => 1}, $pkg->_cleanup_validate_spec(), }, allow_extra => 1, ignore_primary_keys => 1, ); my (%opts_for_cleanup, %common_opts, $remote_peer_creation, $self, %opts_for_register, @pass_opts, $remote_cluster_name, $local_cluster_name, $return_obj ); $pkg->_move_common_cleanup_opts( source => \%opts, target => \%opts_for_cleanup, ); $pkg->_copy_common_component_params( source => \%opts, target => \%common_opts ); my $nacltask_wait = delete $opts{nacltask_wait}; my $nacltask_if_exists = delete $opts{nacltask_if_exists}; my $local_ci = delete $opts{command_interface}; my $was_created = delete $opts{_was_created}; my $is_simplicity_enabled = $param_global->get('IS_SIMPLICITY_ENABLED') || 0; $$was_created = 0; $remote_peer_creation = 1; my $create_options_home_cluster = delete $opts{create_options_home_cluster}; my $create_options_remote_cluster = delete $opts{create_options_remote_cluster}; my $remote_ci = delete $opts{remote_interface}; if ( ( $opts{username} && $opts{password} ) || ( $is_simplicity_enabled == 1 ) ) { $remote_peer_creation = 0; } elsif ($opts{username} || $opts{password}) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Either the password or the username value is missing."); } if ($remote_peer_creation && (!$remote_ci || !$create_options_remote_cluster)) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "The options remote_interface, password, " . "username and create_options_remote_cluster were not passed. " . "These options are required for creating the peer relationship on the remote cluster. " . "For creating the peer relationship on the remote cluster either pass username/password " . "or remote_interface/create_options_remote_cluster combinations" ); } my $random_passphrase = $pkg->random_name_generator(); my %extra_opts; if ( $is_simplicity_enabled == 1 ) { if ( defined $remote_ci ) { my $passphrase_obj = $pkg->SUPER::create( command_interface => $remote_ci, 'generate-passphrase' => 'true', 'ipspace' => $opts{ipspace} || 'Default', ); $extra_opts{'passphrase'} = $passphrase_obj->passphrase(); } else { $Log->warn("Can not generate passphrase on remote cluster as no 'remote_interface' has been specified."); } } my %create_opts_local = ( %opts, %common_opts, %$create_options_home_cluster, command_interface => $local_ci, %extra_opts, ); push @pass_opts, \%create_opts_local; if ($remote_peer_creation) { if (( $local_ci->has_uichange( uichange => 'cluster-peer-authentication' ) ) && ($remote_ci->has_uichange( uichange => 'cluster-peer-authentication' ) ) ) { if (exists $opts{passphrase}) { delete $opts{passphrase} if (!defined $opts{passphrase}); } else { $opts{passphrase} = $random_passphrase; } $create_opts_local{passphrase} = $opts{passphrase} if(defined $opts{passphrase}); } my %create_opts_remote = ( %opts, %common_opts, %$create_options_remote_cluster, command_interface => $remote_ci ); push @pass_opts, \%create_opts_remote; } my @cluster_peer_self_objs; foreach my $opts_to_create (@pass_opts) { my $obj; CREATE: { use warnings qw(exiting); try { $obj = $pkg->SUPER::create(%$opts_to_create); $$was_created = 1; } ## end try catch NACL::C::Exceptions::ClusterPeer::AlreadyExists with { my $exception = $_[0]; $Log->trace('The cluster peer already exists'); my %pass_opts; $pass_opts{cluster} = $exception->get_peer_cluster_name(); $obj = $pkg->_element_exists_handler( create_opts => {%pass_opts,%$opts_to_create}, nacltask_if_exists => $nacltask_if_exists, exception => $exception ); if (!$obj) { no warnings qw(exiting); redo CREATE; } }; } ## end CREATE: push @cluster_peer_self_objs, $obj; } $self = $cluster_peer_self_objs[0]; if ($cluster_peer_self_objs[1]) { $self->remote_cluster_peer($cluster_peer_self_objs[1]); } else { # Only one object returned: must be that the reciprocal form was used. # Created the remote object ourselves. my $local_cluster = NACL::CS::ClusterIdentity->fetch(%common_opts, command_interface => $local_ci,)->name(); if (!$remote_ci) { my @remote_nodes = $self->get_one_state_attribute("remote-cluster-nodes",%common_opts); $remote_ci = NACL::STask::Node->new(node => $remote_nodes[0]); } my $remote_cluster_peer = $pkg->new( command_interface => $remote_ci, cluster => $local_cluster, ); $self->remote_cluster_peer($remote_cluster_peer); } if ($opts_for_cleanup{nacltask_to_cleanup}) { # Register this resource with the Cleanup Manager for cleanup $self->_copy_common_opts_for_cleanup( 'source' => { %opts_for_cleanup, }, 'target' => \%opts_for_register, 'nacltask_to_cleanup' => \$opts_for_cleanup{nacltask_to_cleanup}, 'to_cleanup' => 'purge' ); $self->_register_for_cleanup(%opts_for_register); } #wait_for_creation or wait for availability #this check can be done on any one cluster if ($nacltask_wait) { $cluster_peer_self_objs[0]->wait_on_attribute( attribute_to_check => 'availability', till_value => ['Available'], %common_opts, ); } $Log->exit() if $may_exit; return $self; } ## end sub create =head2 purge # without passing remote_interface as class call NACL::STask::ClusterPeer->purge( command_interface => $command_interface, cluster => $cluster, nacltask_verify => 1, nacltask_if_purged => "pass" ); (or) # passing remote_interface as class call NACL::STask::ClusterPeer->purge( command_interface => $command_interface, remote_interface => $remote_ci, cluster => $cluster, nacltask_verify => 1, nacltask_if_purged => "pass", ); (or) # instance call $Cluster_Peer_Object->purge( nacltask_verify => 1, nacltask_if_purged => "pass" ); (Class or Instance method) This method is used to delete the cluster peer relationship on both the local and remote clusters. This method also handles whether the user requires to do verification or not, by passing nacltask_verify as 1. =over =item Options =over =item C<< remote_interface => $ci >> (Optional for class method calls, Not Applciable for Instance method calls) This is the type of object of the remote cluster. If not specified, the task library constructs this object. =item C<< command_interface => $ci >> (Required for Class call, Not Applicable for Instance call) This is the type of object of the local cluster. =item C<< cluster => $remote_cluster >> (Required for Class call, Not Applicable for Instance call) This is the name of the remote cluster. =item C<< nacltask_verify => $nacltask_verify_boolean >> (Optional) If '0' (default), verification will not be performed. If '1', verification will be performed to ensure that the deletion did happen successfully. =item C<< nacltask_if_purged => $nacltask_if_purged >> (Optional) Default value is 'die'. If 'pass', It will pass if the cluster peer realtionship is already deleted. If 'die', NACL::C::Exceptions::ClusterPeer::DoesNotExist type of exception is raised. =item Other options All of the other various options accepted by L<< NACL::C::ClusterPeer->delete|lib-NACL-C-ClusterPeer-pm/delete >>. =back =back =over =item Exceptions =over =item C This type of exception is thrown when an attempt is made to delete cluster peer relation that does not exists. =item C This type of exception is thrown when verification fails for the deleted cluster peer relation. =back =back =cut sub purge { $Log->enter() if $may_enter; my ($pkg_or_obj, @args) = @_; my %opts = validate_with( params => \@args, spec => { command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface', optional => 1 }, remote_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface', optional => 1 }, cluster => {type => SCALAR, optional => 1}, nacltask_verify => {type => BOOLEAN, default => 0}, nacltask_if_purged => {type => SCALAR, default => 'die'}, }, allow_extra => 1, ); my $nacltask_verify = delete $opts{nacltask_verify}; my $nacltask_if_purged = delete $opts{nacltask_if_purged}; my $remote_cluster = delete $opts{cluster}; my $remote_ci = delete $opts{remote_interface}; my %common_opts; $pkg_or_obj->_copy_common_component_params( source => \%opts, target => \%common_opts ); my @objs; if (ref $pkg_or_obj) { push @objs, $pkg_or_obj; push @objs, $pkg_or_obj->remote_cluster_peer(); } else { # Get local cluster name my $local_cluster = NACL::CS::ClusterIdentity->fetch(%common_opts, command_interface => $opts{command_interface},)->name(); # Get remote cluster name if $remote_cluster is not defined. $remote_cluster = NACL::CS::ClusterIdentity->fetch(%common_opts, command_interface => $remote_ci,)->name() unless (defined $remote_cluster); # Construct remote_ci if not provided if (!$remote_ci) { my $peer_cs = NACL::CS::ClusterPeer->fetch( %common_opts, command_interface => $opts{command_interface}, filter => {cluster => $remote_cluster}, requested_fields => [qw(remote-cluster-nodes)], allow_empty => 1, ); # If not defined, relationship doesn't exist. if (defined $peer_cs) { my @remote_nodes = $peer_cs->remote_cluster_nodes(); $remote_ci = NACL::STask::Node->new(node => $remote_nodes[0]); } } push @objs, $pkg_or_obj->new( command_interface => $opts{command_interface}, cluster => $remote_cluster ); # remote_ci not defined would mean the peer relationship doesn't # exist. Leave it to the call to delete later to handle the # if nacltask_if_purged value. if (defined $remote_ci) { push @objs, $pkg_or_obj->new( command_interface => $remote_ci, cluster => $local_cluster ); } } try { foreach my $cluster_peer_obj (@objs) { if (defined $cluster_peer_obj) { $cluster_peer_obj->delete(); $cluster_peer_obj->_generic_purge_verify() if ($nacltask_verify); } } } catch NACL::C::Exceptions::ClusterPeer::DoesNotExist with { my $exception_obj = $_[0]; if ($nacltask_if_purged !~ /pass/i) { $Log->exit() if $may_exit; $exception_obj->throw(); } }; $Log->exit() if $may_exit; } ## end sub purge 1;