# # Copyright (c) 2014-2015 NetApp, Inc., All Rights Reserved# Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. ## @summary Cabling Task Module ## @author ppranav@netapp.com ## @status ## @pod here package NACL::MTask::Cabling; use strict; use warnings; use Tharn qw( host); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use Exporter qw(import); use Params::Validate qw(:all); use NATE::ProcManager; use NACL::Exceptions::InvalidParam; use NACL::Transit; use NACL::C::StorageDiskOption; use NATE::BaseException; use NATE::BaseException qw(:try); use Storable 'dclone'; use NATE::Exceptions::Argument; use NACL::APISet::Exceptions::UnexpectedOutputException; use vars qw( $VERSION %opts %corrected_disks ); $VERSION = '0.01'; our @EXPORT_OK = qw(cabling_start); =head1 NAME NACL::MTask::Cabling =head1 DESCRIPTION C provides required functionality to do cabling operations for CFT transition. Functionality provided by this library are used after Prepare phase or before REVERT phase in CFT transition work flow. The script using this library need to be run on the reserved client by the test environment for VSIM cabling to run. This lib performs following operations. FORWARD 1. Make ports offline / VSIM disk Import 2. Correct disk names (VSIM only) 3. Assign 7 mode disks to c mode nodes REVERT 1. Correct disk names (VSIM only) 2. Remove ownership of 7 mode disk still assigned to c mode (Will fail if those disks are part of any online aggr) 3. Make ports online (Except for VSIM) 4. Boot 7 mode nodes in maintenance mode 5. Assign 7 mode disks back to 7 mode nodes 6. Boot 7 mode node in normal mode This is lib is corrently supports SINGLEPATH_HA and VSIM might see changes to support other setup types. =head2 cabling_start Method Invocation via Class: $resp = NACL::MTask::Cabling->cabling_start( command_interface_7mode => \@Node7Mode, command_interface_cmode => \@NodeCMode, id7mode => \@Id7Mode, idcmode => \@IdCMode, smode_config => $s_config, direction => "CFT_FORWARD", setup_type => "SINGLEPATH_HA" ); This method performs all the steps required to be performed in Cabling phase for CFT transition. =over =item Options =over =item C<< command_interface_7mode => \@Node7Mode >> (Required) This is required parameter, this parameter provides reference to an array of 7 mode node's command_interface. =item C<< command_interface_cmode => \@NodeCMode >> (Required) This is required parameter, this parameter provides reference to an array of C mode node's command_interface. =item C<< smode_config => $s_config >> (Required) This is required parameter, this parameter provides reference 7 mode configuration hash returned by Prepare lib. =item C<< direction => "CFT_FORWARD" >> (Optional) This parameter gives what type of cabling will be performed FORWARD or REVERT (IPT/CFT). Default value is CFT_FORWARD =item C<< id7mode => ["ha1","ha2"] >> (Optional) This parameter takes input of 7 mode VSIM identifiers. Not used for other type of hardware. =item C<< idcmode => ["cha3","cha4"] >> This parameter takes input of C mode VSIM identifiers. Not used for other type of hardware. (Optional) This parameter gives what type of cabling will be performed FORWARD or REVERT. Default value is CFT_FORWARD =item C<< setup_type => VSIM >> (Optional) This parameter provides for which type of setup the cabling will be done. Default value is VSIM. =back =cut sub cabling_start { my ( $self, %args ) = @_; if ( !$args{direction} || $args{direction} =~ /^CFT/ ) { %opts = validate_with( params => \%args, spec => $self->_cft_validate_spec(), allow_extra => 1 ); } elsif ( $args{direction} =~ /^IPT/ ) { %opts = validate_with( params => \%args, spec => $self->_ipt_validate_spec(), allow_extra => 0 ); } else { NATE::Exceptions::Argument->throw( "Value for parameter direction is :" . $args{direction} . ", but that is invalid" ); } if ( !%opts ) { NATE::Exceptions::Argument->throw("Need mandatory arguments."); } if ( $opts{direction} eq 'CFT_FORWARD' ) { _forward_cabling(); } elsif ( $opts{direction} eq 'CFT_REVERT' ) { _revert_cabling(); } elsif ( $opts{direction} eq 'IPT_FORWARD' ) { _ipt_forward_cabling(); } elsif ( $opts{direction} eq 'IPT_REVERT' ) { # YET TO IMPLEMENT #_ipt_revert_cabling(); } else { NATE::Exceptions::Argument->throw( "Value for parameter direction is :" . $args{direction} . ", but that is invalid" ); } return \%opts; } ###################################################################### ######################### HELPER ROUTINES ############################ ###################################################################### sub process_remaining_disks_on_cmode { my ( $pkg, %args ) = @_; if ( $pkg !~ /Cabling/ ) { %args = @_; } my $node_7mode = $args{node_7mode}; my $node_cmode = $args{node_cmode}; my $apiset = $node_cmode->get_7m_or_nodescope_apiset(); my @cmodedisks; my @smodedisks; my @remaining_disks; _get_disk_name_list_from_node( $node_cmode, 0, \@cmodedisks ); if ( $opts{setup_type} eq 'VSIM' ) { _get_disk_name_list_from_disk_list( $corrected_disks{ $node_7mode->name() }{disks}, \@smodedisks ); } else { _get_disk_name_list_from_disk_list( $opts{smode_config}{ $node_7mode->name() }{disks}, \@smodedisks ); } NACL::C::StorageDiskOption->modify( command_interface => $node_cmode, node => $node_cmode->name(), "autoassign" => "off", ); foreach my $disk_name (@smodedisks) { if ( grep( /$disk_name/, @cmodedisks ) ) { push( @remaining_disks, $disk_name ); } } _remove_disks_ownership_on_apiset( $apiset, \@remaining_disks ); } sub _cft_validate_spec { my %input_validate_spec = ( command_interface_7mode => { type => ARRAYREF }, command_interface_cmode => { type => ARRAYREF }, direction => { type => SCALAR, optional => 1, default => "CFT_FORWARD" }, setup_type => { type => SCALAR, optional => 1, default => "VSIM" }, id7mode => { type => ARRAYREF, optional => 1, default => [ "ha1", "ha2" ], }, idcmode => { type => ARRAYREF, optional => 1, default => [ "cha3", "cha4" ], }, smode_config => { type => HASHREF, }, ); return \%input_validate_spec; } sub _ipt_validate_spec { my %input_validate_spec = ( command_interface_7mode => { type => ARRAYREF }, direction => { type => SCALAR, }, setup_type => { type => SCALAR, optional => 1, default => "VSIM" }, id7mode => { type => ARRAYREF }, idcmode => { type => ARRAYREF }, smode_config => { type => HASHREF, }, destImage => { type => SCALAR, }, buildServer => { type => SCALAR, }, ); return \%input_validate_spec; } sub _forward_cabling { my @Id7Mode = @{ $opts{id7mode} }; my @IdCMode = @{ $opts{idcmode} }; my $LinuxHost = undef; my $stiUser; foreach my $node ( @{ $opts{command_interface_cmode} } ) { NACL::C::StorageDiskOption->modify( command_interface => $node, node => $node->name(), "autoassign" => "off", ); } if ( $opts{setup_type} eq 'SINGLEPATH_HA' ) { #Make the online ports offline on 7 Mode system foreach my $node ( @{ $opts{command_interface_7mode} } ) { _make_ports_offline($node); } #Waiting for the disk changes to be detected by nodes sleep 60; } elsif ( $opts{setup_type} eq 'VSIM' ) { #Do disk import for ( my $index = 0 ; $index < scalar( @{ $opts{command_interface_7mode} } ) ; $index++ ) { if ( defined $opts{command_interface_client} ) { $LinuxHost = $opts{command_interface_client}; $stiUser = ( split( /-|_/, $opts{command_interface_7mode}->[0]->name() ) )[0]; my $conn = $LinuxHost->connect( template => "ssh" ); $conn->execute( command => "su $stiUser", prompts => [ $LinuxHost->{'hostname'} ] ); $conn->execute( command => "/usr/software/test/bin/vsim disk-import " . $IdCMode[$index] . " " . $Id7Mode[$index] . " -smoke -mambo vspc -idbg vmconfig", timeout => 3600, prompts => [ $LinuxHost->{'hostname'} ] ); } else { _exec_linux_cmd( "/usr/software/test/bin/vsim disk-import " . $IdCMode[$index] . " " . $Id7Mode[$index] . " -smoke -mambo vspc -idbg vmconfig" ); } } #Waiting for the disk changes to be detected by nodes sleep 60; _prepare_correct_disk_names( command_interface_7mode => $opts{command_interface_7mode}, command_interface_cmode => $opts{command_interface_cmode}, ); } else { NATE::Exceptions::Argument->throw( "Value for parameter setup_type is :" . $opts{setup_type} . ", but that is invalid" ); } #Assign 7 Mode disks to C mode Nodes for ( my $index = 0 ; $index < scalar( @{ $opts{command_interface_7mode} } ) ; $index++ ) { my $node_7mode = $opts{command_interface_7mode}->[$index]; my $node_cmode = $opts{command_interface_cmode}->[$index]; my @disk_to_assign; if ( $opts{setup_type} eq 'VSIM' ) { _get_disk_name_list_from_disk_list( $corrected_disks{ $node_7mode->name() }{disks}, \@disk_to_assign ); } else { _get_disk_name_list_from_disk_list( $opts{smode_config}{ $node_7mode->name() }{disks}, \@disk_to_assign ); } #Disk show on C Mode system before disk assign _disk_show_all( $node_cmode->get_7m_or_nodescope_apiset() ); _assign_disks_on_apiset( apiset => $node_cmode->get_7m_or_nodescope_apiset(), disk_list => \@disk_to_assign, owner_name => $node_cmode->name(), sysid => _get_system_id($node_cmode), ); #Disk show on C Mode system after disk assign _disk_show_all( $node_cmode->get_7m_or_nodescope_apiset() ); } } sub _revert_cabling { if ( $opts{setup_type} eq 'SINGLEPATH_HA' ) { #Make the offlined ports back online on 7 Mode system foreach my $node ( @{ $opts{command_interface_7mode} } ) { _make_ports_online($node); } #Waiting for the disk changes to be detected by nodes sleep 60; } elsif ( $opts{setup_type} eq 'VSIM' ) { #Dont do anything for VSIM } _prepare_correct_disk_names( command_interface_7mode => $opts{command_interface_7mode}, command_interface_cmode => $opts{command_interface_cmode}, ); #Remove ownership of remaining disk on C Mode system for ( my $index = 0 ; $index < scalar( @{ $opts{command_interface_7mode} } ) ; $index++ ) { process_remaining_disks_on_cmode( node_7mode => $opts{command_interface_7mode}->[$index], node_cmode => $opts{command_interface_cmode}->[$index], ); } #Check if all the disk that needs to be moved back to 7 Mode are unassigned in C Mode #_check_disks_unassigned($opts{command_interface_cmode}); #Assign disks back to 7 Mode foreach my $node ( @{ $opts{command_interface_7mode} } ) { my $apiset_maint = _get_maint_mode($node); my @disk_to_assign; _disk_show_all($apiset_maint); _get_disk_name_list_from_disk_list( $opts{smode_config}{ $node->name() }{disks}, \@disk_to_assign ); _assign_disks_on_apiset( apiset => $apiset_maint, disk_list => \@disk_to_assign, owner_name => $node->name(), sysid => $opts{smode_config}{ $node->name() }{system_id}, ); #Disk show after assigning disk back to 7 Mode _disk_show_all($apiset_maint); #Booting nodes to normal mode NACL::Transit->new( name => $node->name() ) ->change_state( to => 'CLI' ); } } sub _ipt_forward_cabling { my @Nodes = @{ $opts{command_interface_7mode} }; if ( defined( $Nodes[1] ) ) { my $procmgr = NATE::ProcManager->new( proc_info => [ { codespec => \&_do_inplace_transition, args => [ $Nodes[0], $opts{destImage}, $opts{buildServer} ], runid => "Cabling_Node_" . $Nodes[0]->name(), onexit => \&NATE::Process::on_exit_propagate_worst_result }, { codespec => \&_do_inplace_transition, args => [ $Nodes[1], $opts{destImage}, $opts{buildServer} ], runid => "Cabling_Node_" . $Nodes[1]->name(), onexit => \&NATE::Process::on_exit_propagate_worst_result }, ], background => 1, parallel => 1, ); $procmgr->start; $procmgr->wait; my @results = $procmgr->results(); foreach my $res (@results) { foreach ( @{ $res->{messages} } ) { if ( $_ =~ /Installation/i ) { NATE::BaseException->throw($_); } } } my $result = NATE::Result->is_failure( $procmgr->worst_result ); if ($result) { NATE::BaseException->throw( "Failed to perform _ipt_forward_cabling"); } } else { _do_inplace_transition( $Nodes[0], $opts{destImage}, $opts{buildServer} ); } } sub _do_inplace_transition { my $node = shift; my $destImage = shift; my $buildServer = shift; my $aggr_name = $node->node . "_rootAggr"; $aggr_name =~ s/-/_/g; my $Host = host( $node->name() ); my $Transit_obj = NACL::Transit->new( name => $node->node ); my $disks; my @spare_disk; $Transit_obj->change_state( to => 'MAINT' ); my $api_set = NACL::APISet->new( hostobj => $Host, category => "Node", interface => "CLI", set => "Maintenance", ); $api_set->set_timeout( 'timeout' => 120 ); my $parsed_output = $api_set->disk_show( 'owner-name' => $node->node )->get_parsed_output(); foreach ( @{$parsed_output} ) { push( @spare_disk, $_->{'disk'} ); } print "array of disks are ==== @spare_disk\n"; $disks = join( ' ', @spare_disk ); undef $api_set; $Transit_obj->change_state( to => 'BOOT_MENU' ); undef $Transit_obj; my $conn = $Host->connect( template => "console" ); my $prompts = [ "Do you want to continue" => "y", "Select the network port you want to use for the download.*" => $Host->{MGMT_PORT}, "The node needs to reboot for this setting to take effect" => "y", "Enter the IP address for port" => $Host->{MGMT_IP}, "Enter the netmask for port" => $Host->{MGMT_MASK}, "Enter IP address of default gateway:" => $Host->{'MGMT_GATEWAY'}, "What is the URL for the package" => $buildServer . $destImage, "What is the user name on" => "", "subsequent reboots" => "yes", "want to reboot now" => "no", "Booting in" => "\r", ]; try { $conn->execute( command => 7, timeout => 1200, maxbufsize => 0, 'match_table' => $prompts, prompts => ['Selection'] ); } catch NATE::BaseException with { my $exp = shift; $conn->send_intr(); NATE::BaseException->throw( "Installation failed with exception : " . $exp->text() ); }; $prompts = [ "Selection" => "replace_root $aggr_name -d $disks", "Reboot now" => "y", "Press Ctrl-C for" => "\cc", "Are you sure you want to continue" => "yes", "Booting in" => "\r", ]; $conn->execute( command => 8, timeout => 1200, maxbufsize => 0, 'match_table' => $prompts, prompts => ['Do you really want to proceed'] ); $prompts = [ "Selection" => "wipeconfig", "Press Ctrl-C for" => "\cc", "Are you sure you want to continue" => "yes", "Booting in" => "\r", ]; $conn->execute( command => "y", timeout => 1200, maxbufsize => 0, 'match_table' => $prompts, prompts => ['Rebooting to finish wipeconfig request'] ); $prompts = [ "Selection" => '6', "Press Ctrl-C for" => "\cc", "Are you sure you want to continue" => "yes", "Booting in" => "\r", ]; $conn->execute( command => " ", timeout => 1200, maxbufsize => 0, 'match_table' => $prompts, prompts => ['login'] ); sleep 10; $prompts = [ "Type yes to confirm and continue" => 'yes', "Enter the node management interface port" => $Host->{'MGMT_PORT'}, "Enter the node management interface IP address" => $Host->{'MGMT_IP'}, "Enter the node management interface netmask" => $Host->{'MGMT_MASK'}, "Enter the node management interface default gateway" => $Host->{'MGMT_GATEWAY'}, "press Enter to complete cluster setup" => 'exit', "login:" => 'admin', ]; $conn->execute( command => "", timeout => 1200, maxbufsize => 0, 'match_table' => $prompts, prompts => ['::>'] ); $node->{hostrec}->{'hosttype'} = 'filer-ng-vsim'; $conn->execute( command => "system node rename -newname $Host->{'hostname'} ", timeout => 1200, maxbufsize => 0, prompts => [ $Host->{'hostname'} ] ); } #TO BE IMPLEMENTED sub _ipt_revert_cabling { } sub _get_boot_menu { my $node = shift; my $Transit_obj = NACL::Transit->new( name => $node->name() ); $Transit_obj->change_state( to => 'BOOT_MENU' ); } sub _check_disks_unassigned { my $nodes = shift; my @source_list; #primary list to iterate over my @desti_list; #list to check with for ( my $index = 0 ; $index < scalar( @{$nodes} ) ; $index++ ) { my $source_node = $opts{command_interface_7mode}->[$index]; my $desti_node = $nodes->[$index]; _get_disk_name_list_from_node( $desti_node, 1, \@desti_list ); _get_disk_name_list_from_disk_list( $opts{smode_config}{ $source_node->name() }{disks}, \@source_list ); } foreach (@source_list) { if ( !grep( /$_/, @desti_list ) ) { NATE::BaseException->throw( "Disk:" . $_ . " in Prepare lib node config list is not unassigned in the node. Can't go ahead" ); } } } sub _make_ports_offline { my $node = shift; my $resp; $resp = _exec_cmd_maint_mode( $node, 'sysconfig -v' ); while ( $resp =~ /slot.*?SAS Host Adapter\s(\w\w)\s.*?UP/g ) { _exec_cmd_maint_mode( $node, "sasadmin adapter_offline " . $1 ); } while ( $resp =~ /slot.*?FC Host Adapter\s(\w\w)\s.*?UP/g ) { _exec_cmd_maint_mode( $node, "fcadmin offline " . $1 ); } } sub _make_ports_online { my $node = shift; my $resp; $resp = _exec_cmd_maint_mode( $node, 'sysconfig -v' ); while ( $resp =~ /slot.*?SAS Host Adapter\s(\w\w)\s.*?OFFLINE/g ) { _exec_cmd_maint_mode( $node, "sasadmin adapter_online " . $1 ); } while ( $resp =~ /slot.*?FC Host Adapter\s(\w\w)\s.*?OFFLINE/g ) { _exec_cmd_maint_mode( $node, "fcadmin online " . $1 ); } } sub _assign_disks_on_apiset { my %args = @_; my $apiset = $args{apiset}; my $disk_list = $args{disk_list}; my $owner_name = $args{owner_name}; my $sysid = $args{sysid}; foreach ( @{$disk_list} ) { $apiset->disk_assign( 'disk-list' => $_, 'force' => 1, 'owner-name' => $owner_name, 'sysid' => $sysid, ); } } sub _get_maint_mode { my $node = shift; my $Transit_obj = NACL::Transit->new( name => $node->name() ); $Transit_obj->change_state( to => 'MAINT' ); my $host = host( $node->name() ); my $api_set_obj = NACL::APISet->new( hostobj => $host, category => "Node", interface => "CLI", set => "Maintenance", ); $api_set_obj->set_timeout( 'timeout' => 360 ); return $api_set_obj; } sub _exec_cmd_maint_mode { my $node = shift; my $cmd = shift; my $api = _get_maint_mode($node); my $prompts = [ "Are you sure you want to destroy the local mailboxes?" => "yes", "Do you wish to continue?" => "yes", "Volumes must be taken offline. Are all impacted volumes offline(y/n)?" => "yes", "Do you want to continue(y/n)?" => "yes", "Are you sure you want to destroy this aggregate?" => "yes", "Are you sure want to take .*?" => "yes", ]; my $res = $api->execute_command( 'command' => $cmd, 'connectrec-match_table' => $prompts, ); return $res; } sub _get_disk_name_list_from_node { my $node = shift; my $not_owned = shift; my $list_to_return = shift; my $apiset_node = $node->get_7m_or_nodescope_apiset(); my $parsed_output = $apiset_node->disk_show( 'not-owned' => $not_owned, ) ->get_parsed_output(); foreach ( @{$parsed_output} ) { push( @{$list_to_return}, $_->{'disk'} ); } } sub _get_disk_name_list_from_disk_list { my $disk_list = shift; my $list_to_return = shift; foreach ( @{$disk_list} ) { my $disk = $_->{'disk'}; $disk =~ s/^1/0/; push( @{$list_to_return}, $disk ); } } sub _disk_show_all { my $apiset = shift; #TODO convert to try catch eval { $apiset->disk_show(); }; eval { $apiset->disk_show( 'not-owned' => 1, ); }; } sub _remove_disks_ownership_on_apiset { my $apiset = shift; my $disk_list = shift; my $parsed_output; foreach my $disk ( @{$disk_list} ) { $parsed_output = $apiset->disk_remove_ownership( 'disk-list' => $disk, force => 1, ); if ( $parsed_output->{processed_output} =~ /Ownership Remove request failed/i ) { NACL::APISet::Exceptions::CommandFailedException->throw( text => $parsed_output->{processed_output}, response_object => $parsed_output ); } } } sub _exec_linux_cmd { my $cmd = shift; my $resp; my %resp_hash; my $resp_obj; $resp = `$cmd`; $Log->comment($resp); if ( $resp =~ /fatal|error/i ) { $resp_hash{command} = $cmd; $resp_hash{processed_output} = $resp; $resp_obj = bless( \%resp_hash, "NACL::APISet::Response" ); NACL::APISet::Exceptions::CommandFailedException->throw( text => "VSIM command " . $cmd . " has failed.", response_object => $resp_obj, ); } return $resp; } sub _prepare_correct_disk_names { my %args = @_; my $node_7mode = $args{command_interface_7mode}; my $node_cmode = $args{command_interface_cmode}; for ( my $index = 0 ; $index < scalar( @{$node_7mode} ) ; $index++ ) { $corrected_disks{ $node_7mode->[$index]->name() }{disks} = dclone $opts{smode_config}{ $node_7mode->[$index]->name() }{disks}; my @all_disks = _get_all_disks_on_apiset( $node_cmode->[$index]->get_7m_or_nodescope_apiset() ); foreach my $disk_entry ( @{ $corrected_disks{ $node_7mode->[$index]->name() }{disks} } ) { foreach my $disk_entry_correct (@all_disks) { if ( $disk_entry->{serial_number} eq $disk_entry_correct->{serial_number} ) { $Log->comment( "disk name updated from " . $disk_entry->{disk} . " to " . $disk_entry_correct->{disk} ); $disk_entry->{disk} = $disk_entry_correct->{disk}; } } } } } sub _get_system_id { my $node = shift; my $sysid = $node->get_7m_or_nodescope_apiset()->sysconfig()->get_parsed_output() ->[0]->{system_id}; $sysid =~ s/ \(.*\)//; return $sysid; } sub _get_all_disks_on_apiset { my $apiset = shift; my @all_disks; my $parsed_output_owned; my $parsed_output_not_owned; try { $parsed_output_owned = $apiset->disk_show()->get_parsed_output(); } catch NACL::APISet::Exceptions::UnexpectedOutputException with { my $exp = shift; }; try { $parsed_output_not_owned = $apiset->disk_show( 'not-owned' => 1 )->get_parsed_output(); } catch NACL::APISet::Exceptions::UnexpectedOutputException with { my $exp = shift; }; if ($parsed_output_owned) { push( @all_disks, @$parsed_output_owned ); } if ($parsed_output_not_owned) { push( @all_disks, @$parsed_output_not_owned ); } return @all_disks; } 1;