# $Id: $ # # Copyright (c) 2014 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary ConfigValidate Task Module ## @author dl-nacl-dev@netapp.com ## @status shared ## @pod here =head1 NAME NACL::MTask::ConfigValidate =head1 SYNOPSIS NACL::MTask::ConfigValidate->compare( primary_objects => \@list_of_set1, secondary_objects=> \@list_of_set2, ignore_fields => ['vserver'], mandatory_keys => ['size'] ); =head1 DESCRIPTION This NACL::MTask::ConfigValidate Task is for validating configuration of two sets of same type of objects. This Task only supports NACL::C/NACL::CS/NACL::STask objects for comparison. This task takes two same type input objects/array-of-objects and compare them. If all the input objects didn't belong to same type, then task raise a NATE::BaseException. In the case of comparison failure, it raises an NACL::Exceptions::ConfigValidateFailure exception. =over =back =cut =head1 METHODS =head2 compare NACL::MTask::ConfigValidate->compare( primary_objects => \@list_of_set1, secondary_objects => \@list_of_set2, ); or NACL::MTask::ConfigValidate->compare( primary_objects => $vol1, secondary_objects => $vol2, ignore_fields => ['vserver'], mandatory_keys => ['size'] ); Compares two sets of object(primary_objects and secondary_objects) and raise an NACL::Exception::ConfigValidateFailure in failure. This method first fetch the component state object from C/STask objects and uses this CS object for further comparison. The Steps involved in Comparison: 1. Fetch the cs object(if input is not CS object) from both set(primary & secondary objects). 2. if there is only single primary & secondary object then follow step 5. 3. if primary or secondary objects is a list, then create a list of fields(@comparison_keys) for primary comparison. This list includes (primary keys of input object - - ignore_fields + mandatory_keys) in a same order. So if a there is same entry in ignore_fields and mandatory_keys, then mandatory_keys take a priority for comparison. 4. compare both sets of primary objects & secondary objects with @comparison_keys and make a pair of further comparison. 5. Compare the each pair and raise a exception in the case of failure. The exception also contains a hash variable, that represents following data structure. $VAR = { matched => [ { primary_object => $obj1, secondary_object => $obj2 }, {...} ], unmached => { primary_object => $obj3, secondary_object => $obj4 }, partial_matched => [ { primary_object => $obj5, secondary_object => $obj6, unmatched_fields => [ 'field1', 'field2' ] }, {...} ] }; * Matched contains an array of hash of individual matching group. * Unmached contains the array of objects that are totally unmatched. (Primary keys are not matching). * partial_matched represents an array of hash of object, On which primary keys are matching but other fields didn't match(unmatched_fields). =over =item Options: =over =item C<< primary_objects => $obj or \@objs >> (Required, isa NACL::C::/NACL::CS::/NACL::STask::) A single or array of component/STask object of same type that use to compare with secondary_objets. =item C<< secondary_objects => $obj or \@objs >> (Required, isa NACL::C::/NACL::CS::/NACL::STask::) A single or array of component/STask object of same type that use to compare with primary_objets. =item C<< ignore_fields => \@fields >> An arrayref of strings. When comparing two objects, any fields mentioned in this list will be ignored. =item C<< mandatory_keys = \@add_pri_keys >> An arrayref of strings. When comparing two objects, any fields mentioned in this list will use to create a uniq pairs of objects for comparison. =item C<< validate_only_mandatory_keys => 0|1 >> If '0' (Defaults) Validates all the fields. If '1', then create a list of fields(@comparison_keys) for comparison. This list includes (primary keys of input object - ignore_fields + mandatory_keys) in a same order and compare only these @comparison_keys. =item C<< nacltask_allow_empty => 0|1 >> If '0' (Defaults) Task throw an NATE::BaseException, if both primary_objects or secondary_objects lists are empty. If '1', Its allow user to send empty primary_objects or secondary_objects list. =over =back =cut package NACL::MTask::ConfigValidate; 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 validate_with SCALAR ARRAYREF UNDEF HASHREF OBJECT BOOLEAN); use NATE::Exceptions::Argument qw(:try); use NACL::Exceptions::ConfigValidateFailure; use NATE::BaseException qw(:try); use NACL::ComponentUtils qw(Dumper); use Data::Compare; use Class::MethodMaker [ new => [ '-hash', 'new' ], scalar => 'primary_keys', scalar => 'ignore_fields', ]; sub compare { $Log->enter() if $may_enter; my $self = shift; $Log->debug( sub { "Args to NACL::MTask::ConfigValidatation->compare()" . Dumper( @_ ); } ); my %obj_type = (); my $pri_elm = []; my $sec_elm = []; my $validation_spec_pri = sub { my $scalar_or_array = $_[0]; my $state_objs=$pri_elm; if(ref($scalar_or_array) eq 'ARRAY') { for ( my $i = 0; $i <= $#$scalar_or_array; $i++ ) { if (ref( $scalar_or_array->[$i] ) =~ /^NACL::(C|CS|STask)::(.*)/ ) { $obj_type{$2} = 1; if ( $1 ne 'CS' ) { $Log->debug( "Converting NACL::$1::$2 into NACL::CS::$2 Object" ); push @$state_objs, $scalar_or_array->[$i]->state(); }else { push @$state_objs, $scalar_or_array->[$i]; } } else { return 0; } } ## end for ( my $i = 0; $i <= ...) } else { if ( ref($scalar_or_array) =~ /^NACL::(C|CS|STask)::(.*)/ ) { $obj_type{$2} = 1; if ( $1 ne 'CS' ) { $Log->debug("Converting $1 into CS Object"); push @$state_objs, $scalar_or_array->state(); }else { push @$state_objs, $scalar_or_array; } } else { return 0; } } ## end else [ if ( ref($scalar_or_array...))] return 1; }; my $validation_spec_sec = sub { my $scalar_or_array = $_[0]; my $state_objs=$sec_elm; if(ref($scalar_or_array) eq 'ARRAY') { for ( my $i = 0; $i <= $#$scalar_or_array; $i++ ) { if (ref( $scalar_or_array->[$i] ) =~ /^NACL::(C|CS|STask)::(.*)/ ) { $obj_type{$2} = 1; if ( $1 ne 'CS' ) { $Log->debug( "Converting NACL::$1::$2 into NACL::CS::$2 Object" ); push @$state_objs, $scalar_or_array->[$i]->state(); }else { push @$state_objs, $scalar_or_array->[$i]; } ## end if ( $1 ne 'CS' ) } else { return 0; } } ## end for ( my $i = 0; $i <= ...) } else { if ( ref($scalar_or_array) =~ /^NACL::(C|CS|STask)::(.*)/ ) { $obj_type{$2} = 1; if ( $1 ne 'CS' ) { $Log->debug("Converting $1 into CS Object"); push @$state_objs, $scalar_or_array->state(); }else { push @$state_objs, $scalar_or_array; } } else { return 0; } } ## end else [ if ( ref($scalar_or_array...))] return 1; }; my %opts = validate_with( params => \@_, spec => { ignore_fields => { type => ARRAYREF, optional => 1 }, primary_objects => { type => ARRAYREF | OBJECT, callbacks => { "All of the objects provided must be of type 'NACL::(C/CS/STask)::'" => $validation_spec_pri, } }, secondary_objects => { type => ARRAYREF | OBJECT, callbacks => { "All of the objects provided must be of type 'NACL::(C/CS/STask)::'" => $validation_spec_sec } }, mandatory_keys => { type => ARRAYREF, optional => 1 }, validate_only_mandatory_keys => { type => BOOLEAN, default => 0 }, nacltask_allow_empty => { type => BOOLEAN, default => 0 }, }, ); if ( keys(%obj_type) > 1 ) { $Log->exit() if $may_exit; NATE::BaseException->throw( "All of the objects provided for validation are not same type." . Dumper( \%obj_type ) ); } ## end if ( keys(%obj_type) >...) if ( $#$pri_elm != $#$sec_elm) { $Log->exit() if $may_exit; NATE::BaseException->throw( "Number of primary_objects and secondary_objects are not same." . Dumper( \%opts ) ); } ## end if ( $#$pri_elm != $sec_elm) if($opts{nacltask_allow_empty} ) { if($#$pri_elm == -1) { $Log->debug("Both primary_objects & secondary_objects are empty" ." and nacltask_allow_empty is set. So Returning."); $Log->exit() if $may_exit; return; } }else { if($#$pri_elm == -1) { $Log->exit() if $may_exit; NATE::BaseException->throw( "primary_objects and secondary_objects are empty"); } } my $ignore_fields = $opts{ignore_fields} || []; my $mandatory_keys = $opts{mandatory_keys} || []; my $return; # We are always ignoring command_interface push @$ignore_fields, 'command_interface'; # adding ignore_fields my %ignore_param = map { $_ => 1 } @$ignore_fields; $self->ignore_fields( \%ignore_param ); $Log->debug( "List of Ignore fields" . Dumper( \%ignore_param ) ); # Code to get_primary keys my @c_pks; try { my $c_pkg = $pri_elm->[0]->get_C_package_name(); eval "use $c_pkg;"; @c_pks = $c_pkg->get_primary_keys(); my %c_pks_hash = map { $_ => 1 } @c_pks; foreach my $key (@$ignore_fields) { delete $c_pks_hash{$key} if ( exists( $c_pks_hash{$key} ) ); } @c_pks = keys %c_pks_hash; } otherwise { @c_pks = (); }; @c_pks = ( @c_pks, @$mandatory_keys ) if ( defined($mandatory_keys) ); $self->primary_keys( \@c_pks ); $Log->debug( "Keys for maching two sets: " . Dumper( \@c_pks ) ); my %match; for (my $i = 0; $i < scalar(@$pri_elm); $i++){ # for (my $j = 0; $j < scalar(@$sec_elm); $j++){ $match{$i} = { primary_object => $pri_elm->[$i], secondary_object => $sec_elm->[$i], primary_keys_validation => 0 }; if ($self->match_primary_key($pri_elm->[$i], $sec_elm->[$i]) ) { $match{$i}->{primary_keys_validation} = 1; # last; } # } } $Log->debug("All possible matching scenario after primary_keys_validation validation.".Dumper(\%match)) if ( $Log->may_debug() ); while ( my ( $matching_seq, $matching_hash ) = each(%match) ) { if ( $opts{validate_only_mandatory_keys} ) { my $hash=NACL::ComponentUtils->_hash_move( source => $matching_hash, move => [ 'secondary_object', 'primary_object', 'primary_keys_validation' ], map => { primary_keys_validation => 'object_matched' } ); if ( $hash->{object_matched} ) { push @{ $return->{matched} },$hash; } else { push @{ $return->{unmatched} },$hash; } ## end else [ if ( !$matching_hash->...)] } else { if ( $matching_hash->{primary_keys_validation} ) { my @not_matching_fields; if ($self->match_both_objects( %$matching_hash, not_matching_fields => \@not_matching_fields ) ) { $match{$matching_seq}->{object_matched} = 1; push @{ $return->{matched} }, { primary_object => $matching_hash->{primary_object}, secondary_object => $matching_hash->{secondary_object} }; } else { push @{ $return->{partial_matched} }, { primary_object => $matching_hash->{primary_object}, secondary_object => $matching_hash->{secondary_object}, unmatched_fields => \@not_matching_fields }; } ## end else [ if ( $self->match_both_objects...)] } else { push @{ $return->{unmatched} }, { primary_object => $matching_hash->{primary_object}, secondary_object => $matching_hash->{secondary_object} }; } ## end else [ if ( $matching_hash->{...})] } ## end else [ if ( $opts{validate_only_mandatory_keys...})] } ## end while ( my ( $matching_seq...)) if ( exists $return->{unmatched} || exists $return->{partial_matched} ) { $Log->exit() if $may_exit; require Data::Dumper; $Log->comment("ConfigValidate task failed."); $Log->comment("Unmatched ".Dumper($return->{unmatched})); $Log->comment("Partial_matched ".Dumper($return->{partial_matched})); NACL::Exceptions::ConfigValidateFailure->throw( "ConfigValidate task failed", return_param => $return ); } ## end if ( exists $return->{...}) $Log->exit() if $may_exit; } ## end sub compare sub match_primary_key { $Log->enter() if $may_enter; my $self = shift; my ( $obj1, $obj2 ) = @_; $Log->debug( sub { "Args to NACL::MTask::ConfigValidatation match_primary_key" . Dumper($obj1) . Dumper($obj2); } ); my $primary_keys = $self->primary_keys(); foreach my $key (@$primary_keys) { if ( !( exists( $obj1->{$key} ) && exists( $obj2->{$key} ) ) ) { $Log->debug( "$key not exists in both objects. So ignoring this key for validation" ); } $Log->debug( "$key values \n" . Dumper($obj1->$key()) . " \n" . Dumper($obj2->$key()) ); unless (Compare($obj1->{$key}, $obj2->{$key}) ) { $Log->debug( "$key value did'nt match " . Dumper($obj1->$key()) . ' != ' . Dumper($obj2->$key()) ); $Log->exit() if $may_exit; return 0; } ## end unless (Compare($obj1->$key(), ...) } ## end foreach my $key (@$primary_keys) $Log->exit() if $may_exit; return 1; } ## end sub match_primary_key sub match_both_objects { $Log->enter() if $may_enter; my $self = shift; my %opts = @_; $Log->debug( sub { "Args to NACL::MTask::ConfigValidatation match_both_objects" . Dumper( \%opts ); } ); my $_state_obj1 = $opts{primary_object}; my $_state_obj2 = $opts{secondary_object}; my $not_matching_fields = $opts{not_matching_fields}; my $ignore_param = $self->ignore_fields(); my %cmp = map { $_ => 1 } ( keys %$_state_obj1, keys %$_state_obj2 ); for my $key ( keys %cmp ) { if ( exists( $ignore_param->{$key} ) ) { delete( $cmp{$key} ); next; } if(exists $_state_obj1->{$key} ){ my $ret = Compare( $_state_obj1->{$key}, $_state_obj2->{$key} ); $Log->debug( "Data::Compare for result for Field $key is $ret." . Dumper( $_state_obj1->{$key} ) . Dumper( $_state_obj2->{$key} ) ); if ($ret) { delete( $cmp{$key} ); } } } ## end for my $key ( keys %cmp) if (%cmp) { $Log->debug( "matching of both objects failed unmatching fields." . Dumper( \%cmp ) ); @$not_matching_fields = keys %cmp; $Log->exit() if $may_exit; return 0; } ## end if (%cmp) $Log->debug("matching of both objects Passed."); $Log->exit() if $may_exit; return 1; } ## end sub match_both_objects 1;