# $Id$ # # Copyright (c) 2001-2016 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @burt 1052756 NACL::CS::SystemHaInterconnectchannel Initial Check-Ins ## @burt 1055335 Implement NACL::CS::SHIC ## @burt 1058068 Dynamic Channel Allocation for NACL::CS::SHIC ## @burt 1067401 Implement Sinai Hack in 9.3 ## @burt 1078360 Add routine to print table with channel status # ## @summary SystemHaInterconnectChannel ComponentState Module ## @author ng-interconnect-qa ## @status OPEN package NACL::CS::SystemHaInterconnectChannel; use strict; use warnings FATAL => 'all'; # General Package Imports use List::MoreUtils qw(any none); use NACL::InstantComponent qw(instant_component); use NATE::Log qw(log_global); use Params::Validate qw(validate :types); # Commonly Used Exceptions use NACL::Exceptions::InvalidFilterField qw(:try); use NACL::Exceptions::NoElementsFound qw(:try); use NACL::APISet::Exceptions::InvalidParamValueException qw(:try); use base 'NACL::CS::ComponentState::ONTAP'; use Class::MethodMaker [ # To return list of channels that exist array => 'channels', # To match object with node and HAIC port scalar => 'node', scalar => 'port_name', # Attributes/Accessor methods for channels will be created for found # channels so that they are not hard-coded here. ]; my ( $Allow_Empty_Copy, $Log, $May_Enter, $May_Exit, $Methods_Created, ); $Log = log_global(); $May_Enter = $Log->may_enter(); $May_Exit = $Log->may_exit(); $Methods_Created = 0; ################################################################################ # Public Routines ################################################################################ sub fetch { $Log->enter() if $May_Enter; my (%opts, $pkg, @state_objs, ); $pkg = shift; %opts = validate(@_, $pkg->_fetch_validate_spec()); $Allow_Empty_Copy = $opts{allow_empty}; @state_objs = $pkg->SUPER::fetch( @_, choices => [ { method => '_fetch_channel_show_cli', interface => 'CLI', set => 'CMode' }, ], exception_text => 'No matching elements found', ); $Log->exit() if $May_Exit; return wantarray ? @state_objs : $state_objs[0]; } # End fetch() ################################################################################ # Function : channel_table() # Description : # Returns string with a table of the found channels and whether or not they # are in 'established' state. # Arguments : None # Returns : # scalar - string - Text with table # Exceptions : NATE::BaseException - If fetch() has not been run yet. ################################################################################ sub channel_table { require Text::TabularDisplay; my (@channels, %channel_status, $self, @table_data, @table_header, ); $self = shift; if (!defined($self->channels())) { NATE::BaseException->throw('fetch() has not been executed yet.'); } # Get list of channels and status for each channel @channels = $self->channels(); foreach my $channel (@channels) { $channel_status{$channel} = $self->$channel() ? 'true' : 'false'; } # Format data for Text::TabularDisplay and return text table @table_header = ('Channel (' . $self->port_name() . ')', 'Is Established'); @table_data = map {[$_, $channel_status{$_}]} sort(keys(%channel_status)); return Text::TabularDisplay->new(@table_header)-> populate(\@table_data)->render(); } # End channel_table() ################################################################################ # Private Routines ################################################################################ ################################################################################ # Function : _fetch_channel_show_cli() # Description : # TBD # Arguments : # command_interface - object - (required) Value of command_interface # apiset - object - (optional) Value of apiset # filter - hash - (optional) Value of filter # requested_fields - array - (optional) Value of requested_fields # Returns : array # Exceptions : ################################################################################ sub _fetch_channel_show_cli { $Log->enter() if $May_Enter; my (%filter, %master_opts, @node_list, @objs, $pkg, ); $pkg = shift; %master_opts = validate(@_, $pkg->_fetch_backend_validate_spec()); %filter = %{delete($master_opts{filter})}; # If no node filter is specified by user then use the local node if (!defined($filter{node})) { $Log->debug('No node filter specified - filtering on ci'); $filter{node} = $master_opts{command_interface}->name(); } # Populate the list of nodes we will be iterating over. @node_list = split(qr/[|]/, $filter{node}); $Log->debug("List of filtered nodes: @node_list"); # Here, break up work by node requested. This helps by letting # _fetch..._worker() focus on a single node, making workflow simpler FILTERED_NODES_LOOP: foreach my $node_name (@node_list) { my (@state_objs, ); @state_objs = _fetch_channel_show_cli_worker( $pkg, filter => { node => $node_name, }, %master_opts, ); if (@state_objs) { push(@objs, @state_objs, ); } } # End FILTERED_NODES_LOOP $Log->exit() if $May_Exit; return @objs; } # End _fetch_channel_show_cli() ################################################################################ # Function : _fetch_channel_show_cli_worker() # Description : # TBD # Arguments : # command_interface - object - (required) Value of command_interface # filter - hash - (optional) Value of filter # requested_fields - array - (optional) Value of requested_fields # Returns : hashref - single object # Exceptions : # NACL::Exceptions::InvalidFilterField - if requested field is invalid ################################################################################ sub _fetch_channel_show_cli_worker { $Log->enter() if $May_Enter; my ( @chan_names, @chan_ips, @chan_states, @chan_ports, $channel_info, %channel_list, $command_interface, $filter, $haic_driver, @info_hashes, $ip_info, $node_name, %opts, @objs, $pkg, @requested_fields, ); $pkg = shift; %opts = validate(@_, $pkg->_fetch_backend_validate_spec); $command_interface = $opts{command_interface}; $filter = $opts{filter}; @requested_fields = @{$opts{requested_fields}}; $node_name = $filter->{node}; # Because of the way MCC-VSIM displays channel information, we need to get # the partner's HAIC ports' IP Addresses in order to filter the channels # we really need. $ip_info = _get_partner_ip_info( command_interface => $command_interface, node_name => $filter->{node}, ); SINAI_HACK_SETUP: { my ($response, ); $response = $command_interface->apiset()->system_ha_interconnect_config_show( node => $command_interface->name(), )->get_parsed_output(); if ($response->[0]->{transport} =~ /sinai/i) { $haic_driver = 'sinai'; } else { $haic_driver = 'other'; } } # End SINAI_HACK_SETUP # Now, initialize a hashref for each port we've found and store these in an # array. for my $i (0..$#{$ip_info}) { $info_hashes[$i] = { node => $node_name, }; } # Now that we have the IP address of the partner side of the HAIC ports, we # can run 'system ha interconnect channel show' on the requested node(s) $channel_info = $command_interface->apiset()->system_ha_interconnect_channel_show( node => $node_name, )->get_parsed_output(); # 'channel show' just gives us a bunch of lists. So, we need to collate all # of this data into something that is usable. @chan_ips = @{$channel_info->[0]->{'ipaddress'}}; @chan_names = @{$channel_info->[0]->{'channel-name'}}; @chan_ports = @{$channel_info->[0]->{'port-name'}}; @chan_states = @{$channel_info->[0]->{'channel-state'}}; PER_CHANNEL_LOOP: for my $i (0 .. $#chan_ips) { # Looping through the list of channels+states we have received, see if # the channel belongs to an HAIC port. If so, store its data, otherwise # ignore. Each element of @info_hashes will become a CS object for each # HAIC link on the current node. # # Hack for Sinai: # This was originally written to assign channels to their respective ports # by comparing IP addresses on the port vs what's reported in 'channel show'. # However, Sinai does not assign IPs to its HAIC ports. Since Sinai only # has one port, we'll just bypass the IP comparison. This assigns the # channel to the single existing port. IP_LOOP: for my $j (0..$#{$ip_info}) { if ( $haic_driver eq 'sinai' || # Hack for Sinai (see above) $chan_ips[$i] eq $ip_info->[$j] ) { my ($state, ); # To create list of channel names found $channel_list{$chan_names[$i]} = 1; # Store port_name for current port. $info_hashes[$j]->{'port-name'} = $chan_ports[$i]; # Store channel state. Channel state is the only thing we're # supporting with 'requested-fields'. "node" and "port-name" # will always be provided so that requested channels can be # associated with their respective ports. $state = ($chan_states[$i] eq 'established') ? 1 : 0; if (!@requested_fields || any {$chan_names[$i] eq $_} @requested_fields) { $info_hashes[$j]->{$chan_names[$i]} = $state; } } } # End IP_LOOP } # End PER_CHANNEL_LOOP # Store the channel name list that we found above if it was requested or no # 'requested_fields' explicitly defined. foreach my $info_hash (@info_hashes) { if (!@requested_fields || any {'channels' eq $_} @requested_fields) { $info_hash->{channels} = [keys(%channel_list)]; } } # Create accessor methods for the channels that we've found. Can only do # this once, otherwise error. if (!$Methods_Created) { Class::MethodMaker->import([scalar => [keys(%channel_list)]]); $Methods_Created++; } # Check if any of the requested fields is invalid. This has to be done after # the MethodMaker->import() call above since the attributes for the channels # are created there. $pkg->_check_for_invalid_requested_filter_fields( command_interface => $command_interface, filter => $filter, requested_fields => \@requested_fields, ); # Now, create and populate a CS object to return for each port that we've found. CS_CREATE: for my $i (0..$#{$ip_info}) { # What we're doing here - we've initialized an entry in @info_hashes for # each possible link. However, if some filter prevents all entries from # being filled out at this point, let's not create a partial CS object. # So, look to see if more than 2 keys are defined in the hash. "node" # and "port-name" are populated by default. If anything else is defined, # assume the object is good to keep. if ( scalar(keys(%{$info_hashes[$i]})) > 2 ) { push(@objs, $pkg->new(command_interface => $command_interface)); $objs[-1]->_set_fields(row => $info_hashes[$i]); } } # End CS_CREATE $Log->exit() if $May_Exit; return @objs; } # End _fetch_channel_show_cli_worker() ################################################################################ # Function : _get_partner_ip_info() # Description : # Gets IP Address information for the partner node's HAIC ports. # Arguments : # command_interface - object - (Required) Value of command_interface # node_name - scalar - (Required) Value of filter # Returns : arrayref - list of HAIC IP Addresses # Exceptions : ################################################################################ sub _get_partner_ip_info { require NACL::STask::Node; my (%args, $config_cs, $partner_name, ); %args = @_; $partner_name = NACL::STask::Node->get_partner( command_interface => $args{command_interface}, node => $args{node_name}, ); instant_component( command_interface => $args{command_interface}, component_name => 'NACL::C::SystemHaInterconnectConfig', ); $config_cs = NACL::CS::SystemHaInterconnectConfig->fetch( command_interface => $args{command_interface}, filter => { node => $partner_name, }, requested_fields => ['port-name', 'ipaddress', ], ); return $config_cs->ipaddress(); } # End _get_partner_ip_info() 1; ## @pod here =head1 NAME NACL::CS::SystemHaInterconnectChannel =head1 DESCRIPTION C is a derived class of L. It represents the state of HA Interconnect Channels. Unless a node is specified in the filter, the information returned is limited to the context of the node 'command_interface' belongs to. =head1 ATTRIBUTES The following elements are the attributes of the SystemHaInterconnectChannel ComponentState. =over =item C<< channels >> (Array) List of Channels that are defined on the system. Returns a list of Channels that are defined on the system, regardless of any Channel's state. =item C<< node >> The node for which this ComponentState relates. Filled in for CMode CLI. =item C<< port_name >> The name of the HAIC port for which this ComponentState relates. Filled in for CMode CLI. =item C<< Echannel nameE >> Attributes are also created for each channel that is found. This list is created dynamically, so the channels are not included here in the event they change. If 'requested_fields' is not specified, the list of found channels will be returned in the 'channels' attribute. The return value for each channel will be one of the following: =over =item TRUE value (1) If the Channel's State is "established" =item FALSE value (0) If the Channel's state is not "established". =back The result of using dynamic channel allocation is that code checking specific channels must be handled carefully. To simply check the status of all existing channels, you can do this: =over Cchannels()}) { $channel_status = $cs-E$channel_name(); }> =back However, there is a problem for scripts that may only care about a specific channel. If you try this: =over C<$ofw_status = $cs-Eofw();> =back all will be fine...until the channel called 'ofw' is either removed or renamed (or, worst case, disappears unexpectedly). If this happens, the same call will throw an exception because Class::MethodMaker did not create a method for the 'ofw' channel. For script safety (and better logging of the issue found), the following should be done if looking for specific channels: =over Cchannels()}) { $Driver-Eerror('The channel \'ofw\' does not seem to exist!!! File a new BURT'); } else { $ofw_status = $cs-Eofw(); }> =back =back =cut =head1 METHODS =head2 fetch my @channels = NACL::CS::SystemHaInterconnectChannel->fetch(command_interface=>$ci,...); see L Supports CMode CLI. =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 =cut