#Hey emacs, this is basically -*- perl -*- #$Id: //depot/prod/test/Rsierranevada/ontap/main/lib/blocks_filer.pm#1 $ # #Copyright (c) 2002 Network Appliance, Inc. #All rights reserved. # ## @summary Library of Filer functions for blocks machines ## @description ## These test functions implement FCP, iSCSI and VLD features ## of Data ONTAP to support Block I/O (SAN environments). ## These functions are based on the following documents: ## ## Functional specifications at: ## http://web.netapp.com/engineering/projects/releases/sledgehammer/specifications/misc_commands.html ## http://web.netapp.com/engineering/projects/releases/sledgehammer/specifications/mid-ui.html (scrimshaw) ## @usage ## use blocks_filer ; ## ## The 'applies_to_filer_' functions that detect missing required hardware or software versions ## will exit the test script as "Not Applicable". ## (Note that no 'filer_is_' functions terminate that way.) ## ## Functions that detect missing required arguments or invalid arguments ## will exit the test script as a "SCRIPT ERROR". ## ## As all of these functions implement operations on a filer, ## they begin with 'filer_' or 'applies_to_filer_'. ## Other function name prefix values are allowed, but these functions are likely ## to be moved to a more appropriate package in the future. ## @keywords SAN FCP iSCSI VLD Blocks LUN ## @dependencies Filer: ## FilerConfig=filer ## @dependencies Licenses: ## FCP=OK ## iSCSI=OK ## @dependencies Inter-test: ## ClearsStats=NO ## DoesReboot=NO ## UsesConsole=Yes ## UsesStats=No ## @burt 65979 should identify expected message text ## @burt 66069 license command disappears ## @burt 66192 fcp/FCP iscsi/iSCSI capitalization problems ## @burt 68909 lun help for 'serial' and 'setup' subcommands ## @burt 73589 nospacereserve in lun create is meaningless/confusing ## @burt 75772 Typo: iniator in 'lun help show' ## @burt 70828 dup'ed by 71366 ## @burt 103847 iscsi help should work on any vfiler context ## @burt 103943 'lun stats -o' output heading should be consistent (kbytes) or KBytes ## @burt 104271 igroup help and lun help should work on any vfiler context ## @burt 105780 A consistent message is needed for unrecognized 'lun' subcommands ## @author pressley@netapp.com (Blocks QA) ## @status public ## @change 2002-02-05 hedstrom: created ## @change 2002-02-08 hedstrom: document $fc use, add applies_to functions ## @change 2002-02-11 hedstrom: rename package ## @change 2002-02-15 hedstrom: major clean up, 'priv set diag' back in ## @change 2002-02-18 hedstrom: add vld functions, look for debug output ## @change 2002-02-25 hedstrom: vdisk to LUN rename, function name conventions ## @change 2002-03-05 hedstrom: add cluster functions (move later?) ## @change 2002-03-12 jhayden: moved cluster stuff to filer.pm ## @change 2002-03-27 hedstrom: function name conventions ## @change 2002-03-29 hedstrom: move service stuff to filer.pm ## @change 2002-04-02 hedstrom: add filer_fcp_nodename_get filer_fcp_nodename_set ## use filer_cli_say ## @change 2002-04-03 hedstrom: burt68909 in filer_lun_help ## @change 2002-04-03 hedstrom: export filer_lun_show ## @change 2002-04-16 hedstrom: add to filer_fcp_help ## @change 2002-04-18 hedstrom: clean up exported functions, drop package ## @change 2002-05-08 hedstrom: add queue depth to fcp stats output ## @change 2002-05-14 hedstrom: UI change to fcp show initiator output ## @change 2002-06-14 sli: modify function filer_fcp_initiator_check by adding ## an additional parameter @initwwpns, which return 1 if initiator's wwpn ## was shown, else return 0 ## @change 2002-06-25 hedstrom: support temporary licenses, check for valid lun_path arg ## @change 2002-07-10 hedstrom: support new 'lun comment' output format ## @change 2002-07-12 hedstrom: drop nospacereserve support (burt 73589) ## @change 2002-09-23 hedstrom: 80398 replaces 73589 for fullsail ## @change 2002-09-27 hedstrom: Space Reserved output on lun show is back for fullsail ## @change 2002-10-01 hedstrom: add filer_iscsi_help ## @change 2002-10-03 hedstrom: add filer_iscsi_nodename_get filer_iscsi_nodename_set ## @change 2002-10-09 hedstrom: fix filer_fcp_initiator_check to detect no WWPNs specified ## @change 2002-10-09 taojin: add filer_iscsi_adapter_check filer_iscsi_adapters_check ## @change 2002-10-12 taojin: add filer_iscsi_initiator_check ## @change 2002-10-15 taojin: add filer_iscsi_adapter_status_get filer_iscsi_adapter_status_check filer_iscsi_adapter_online filer_iscsi_adapter_offline ## @change 2002-10-16 hedstrom: remove duplicate function, sort functions alphabetically ## @change 2002-10-17 taojin: add filer_iswt_help filer_iswt_interface_status ## filer_iswt_interface_status_check filer_iswt_interface_enable ## filer_iswt_interface_disable filer_iswt_session_show_output_parse ## filer_iswt_session_check filer_iswt_session_verbose_check ## @change 2002-10-21 hedstrom: add filer_iscsi_adapter_stats_get filer_iscsi_adapter_stats_zero ## @change 2002-10-23 hedstrom: add quotes to igroup names if needed ## @change 2002-10-31 jpittman: add filer_iscsi_adapter_stats_check ## filer_iscsi_adapter_stats_field_get filer_iscsi_adapter_stats_get ## filer_iscsi_adapter_stats_zero ## @change 2002-11-15 hedstrom: consoliate missing help text messages to avoid 'Burtless Errors' from nuffo.thpl ## @change 2002-11-22 hedstrom: add missing filer_iscsi_adapters_get function ## @change 2002-11-23 hedstrom: fix messages for missing status output ## @change 2002-12-02 hedstrom: fix FCP status checking ## @change 2002-12-08 hedstrom: add fcp trace functions ## @change 2002-12-10 hedstrom: support new minimum lun size in filer_lun_create ## @change 2003-01-06 hedstrom: add filer_nodename_luns_get ## @change 2003-01-07 hedstrom: support for ostype parameter and min size on lun create ## @change 2003-03-06 hedstrom: issue soft error if FCP are adapters not defined in the test bed ## @change 2003-03-06 hedstrom: Handle up to four port IP cards in filer_iswt_interface_status ## @change 2003-03-06 hedstrom: fix filer_lun_stats_get, make 'all' argument optional ## @change 2003-04-03 hedstrom: support 'no space' in filer_lun_maxsize_get ## @change 2003-04-09 hedstrom: add filer_vol_snap_luns_vaporize ## @change 2003-04-16 sli: add filer_igroup_lun_ids_get to get array of lun id num for a particular igroup, fix problem in filer_igroup_lun_lunid_get ## @change 2003-04-17 hedstrom: add filer_lun_df functions ## @change 2003-04-17 hedstrom: ignore case in iSCSI nodenames ## @change 2003-05-16 hedstrom: add filer_fcp_cfmode_get filer_fcp_cfmode_set functions ## @change 2003-08-01 hedstrom: see burt 101384, lun df does not allow -h option unless it is a debug kernel ## @change 2003-08-13 hedstrom: drop config_filer use ## @change 2003-09-05 hedstrom: update expected output format of 'iswt show adapter' command ## @change 2003-10-12 hedstrom: add iSCSI iSNS functions ## @change 2003-10-22 hedstrom: filer_lun_get replaces filer_lun_show ## @change 2003-10-22 hedstrom: rename functions with 'lun_id' to use 'lunid' ## @change 2003-12-01 hedstrom: handle longer timeout for lun stats commands with many LUNs ## @change 2004-01-29 hedstrom: burt 109997 - use interface to allow filer_cli_say requests to retry only when a repeated command would not fail after the first invocation. ## @change 2004-04-27 hedstrom: added missing functions to EXPORT list, define blocks_filer package ## @change 2004-08-09 hedstrom: increase timeout on filer_lun_clone_wait (burt138208) ## @change 2004-08-09 hedstrom: Determine the percent complete from the known lun clone show status output (burt138209) ## @change 2004-09-03 hedstrom: add filer_fcp_adapters_slot_get, fix filer_fcp_adapters_get ## @change 2004-09-10 hedstrom: remove deprecated tstlog use, begin changes to ## not issue a fatal error if the requested state has already been reached ## @change 2004-10-04 hedstrom: make filer_igroup_destroy ignore already deleted igroup ## @change 2004-11-02 hedstrom: begin ZAPI support ## @change 2004-11-03 hedstrom: fix adapter count in filer_fcp_stats_check ## @change 2004-11-09 hedstrom: remove deprecated functions: filer_igroup_vsa_get filer_igroup_vsa_set ## @change 2004-11-16 hedstrom: burt 146130 - plural form of 'iscsi help show initiators' should be supported ## @change 2004-11-29 hedstrom: support multiple sessions/iSCSI node in filer_iscsi_initiator_check ## @change 2005-01-04 hedstrom: add filer_nodename_lunid_lun_get ## @change 2005-01-06 hedstrom: FILER_ISCSI_TARGET_NAMES no longer has values in Tsingtao ## @change 2005-01-07 korry: add filer_iscsi_session_check, filer_iscsi_session_show_output_parse, filer_iscsi_session_verbose_check subroutines in Tsingtao ## @change 2005-01-12 olinger: Add filer_lun_maps_by_igroup_get(). ## @change 2005-01-24 pressley: Changed return string checked in filer_lun_clone_percentcomplete_get(). ## @change 2005-02-09 hedstrom: Replace "Unexpected error" with "Unexpected message" ## @change 2005-02-17 korry: removed deprecated filer_iswt_* and filer_iscsi_adapter_* subroutines ## added filer_iscsi_interface_enable, filer_iscsi_interface_disable ## filer_iscsi_interface_status,filer_iscsi_interface_status_check ## filer_iscsi_stats_check, filer_iscsi_stats_field_get ## filer_iscsi_stats_get, filer_iscsi_stats_zero ## @change 2005-03-09 groa: Added filer_lun_dbedit_* family of functions. ## @change 2005-03-15 hedstrom: extend timeout on 'igroup show', 60 secs was not enough ## @change 2005-03-15 korry: changed priv level for 'lun config_check' to admin in tsingtao ## @change 2005-03-16 groa: added filer_iscsi_security_default_get/set functions. ## for filer_isns_get, fill in the values in the %data return hash ## @change 2005-03-21 hedstrom: fix ZAPI create lun from snapshot ## @change 2005-03-23 korry: add additional 'VTIC stats messages' to support in tsingtao ## @change 2005-03-30 korry: remove dup routines and ':' from @summary tag. ## @change 2005-03-30 korry: add filer_iscsi_alias_* , filer_iscsi_portal_* and filer_iscsi_tpgroup_* ## routines to handle both CLI and ZAPI interfaces. ## @change 2005-05-03 korry: added filer_iscsi_security_initiator_* and filer_iscsi_security_generate_passwd functions. ## added ZAPI interfaces support to the following functions: ## filer_iscsi_security_*, filer_iscsi_nodename_*, filer_iscsi_session_* . ## @change 2005-05-25 korry: added single_image cfmode to filer_fcp_cfmode_get function ## added quote to special characters in filer_lun_create_from_snapshot function. ## @change 2005-06-01 archana: Updated author tag ## @change 2005-08-19 hedstrom: use filer_name function to extract filer host name from connection ## @change 2005-07-27 korry: added filer_iscsi_status_check ## @change 2005-10-27 korry: modified filer_lun_create to take ostype as mandatory on GB. ## @change 2005-11-30 korry: add ZAPI support for ZAPI testing in AS.4 ## @change 2006-01-05 korry: fix burt190595 for test daemon ## @change 2006-01-11 korry: Enhance to support GB release testing ## @change 2006-02-27 korry: Add filer_igroup_alua_get and set to support ALUA in GB. ## @change 2006-04-25 korry: Add filer_igroup_show_verbose ## @change 2006-07-10 korry: Add filer_igroup_rename ## @change 2006-08-17 korry: Add fcp_stats_field_interrupts field and filer_fcp_adapter_interrupt_get () ## @change 2006-11-03 korry: Add filer_igroup_throttle_reserve_get/set and filer_igroup_throttle_borrow_get/set ## @change 2007-02-13 korry: Add aluadb, config, dbedit, select, and vtoc to filer_lun_help ## @change 2007-05-07 korry: Add ZAPI support for igroup bind and unbind ## @change 2007-05-08 korry: Add conformance nodename to create_random_nodename() ## @change 2007-08-23 korry: modify filer_igroup_show_get() to get correct parsing of igroups ## @change 2007-08-29 korry: modify to allow warning message when disable igroup throttle ## @change 2007-11-02 korry: modify to support both ZAPI and CLI for filer_fcp_adapter_* ## @change 2008-03-21 korry: add checking for fcp adapter up but not connected (burt288435) ## @change 2008-04-18 sanap: added Adapter Qfulls fcp stats field (burt292552) ## @change 2008-09-19 korry: changed filer_fcp_is_installed and filer_iscsi_is_installed to use hostrec params (burt316504) ## @change 2008-09-14 sanap: Added SFP stats for fcp stats fields # When adding new functions, please: # 1) Maintain the alphabetical order of the functions # 2) Define new function names that start with filer_ and # 3) Add additional descriptive nouns to the function name to define the # portion of the filer that the new function deals with and # 4) End the function name with a verb. (Try to reuse the same verbs that appear on other functions) # The following verbs are reserved for functions that can cope with unknown initial states: # create (see make) # Create a new object if it does not exist. # Report a non-fatal error if the object existed to begin with. # destroy (see vaporize) # destroys an object. Reports a fatal failure if the object still exists at the end. # Reports a non-fatal error if the object did not exist to begin with. # initialize # Completely sets up a service, returns 0 if the service is not available or cannot be started # make (see create) # Create a new object if it does not exist. # Report a fatal error if the object does not exist at the end. # Normal instruction tracing will show that the object may have existed to start with. # vaporize (see destroy) # destroys an object. Reporting a fatal failure if the object still exists at the end. # Normal instruction tracing will show that the object may not have existed to start with. # 5) Write functions to meet specific needs of a filer user, rather than to simply expose # the filer interface to our test scripts. This should allow us to write test scripts from # an operational perspective, rather than from the low level CLI. This will permit us to: # a) maintain those operational functions within this package # while minimizing changes to individual test scripts. # b) track burts from within each function rather than in # all of the test scripts that use the function. # 6) Check input parameter values. Use ExitScriptError to report out of range values. # 7) Use ExitError to only report # a) the inability to attain a requested filer state. # Thus, there is NO FATAL ERROR for situations where the # state is attained prior to calling the function. # b) an unexpected change in filer state that is serious # enough as to prevent further testing (for example, # lost of the ability to communicate with the filer). # 8) Use ExitSoftError to report other unexpected results. # 9) Include authors to review newly added code. print("Starting ".__FILE__."\n"); use strict; package blocks_filer; use config_base; use filer; use ZapiUtil; # Has zapi support use Tharn; use Filer::Version qw (:releases get_wafl_version); use vars qw(@ISA @EXPORT @FILER_FCP_TARGET_NAMES @FILER_FCP_TARGET_PARTNERS @FILER_ISCSI_TARGET_NAMES @filer_fcp_stats_fields @filer_fcp_stats_message_fields @filer_fcp_stats_vtic_fields @filer_fcp_adapter_fields @filer_fcp_adapter_verbose_fields @filer_fcp_adapter_verbose_with_virtual_port_fields @filer_fcp_adapter_verbose_zapi_fields @filer_fcp_adapter_verbose_with_virtual_port_zapi_fields @filer_iscsi_stats_fields @filer_iscsi_adapter_fields_iswt @filer_iscsi_adapter_fields_hw $FILER_TEST_UI ); require Exporter; @ISA = ('Exporter'); # Only export functions for use outside of this package @EXPORT = qw( applies_to_filer_with_fcp_installed applies_to_filer_with_fcp_or_iscsi_installed applies_to_filer_with_iscsi_installed create_random_nodename fcp_nodename_new filer_command_help filer_fcp_adapter_buswidth_get filer_fcp_adapter_cachesize_get filer_fcp_adapter_check filer_fcp_adapter_clockspeed_get filer_fcp_adapter_crcerrs_get filer_fcp_adapter_desc_get filer_fcp_adapter_dlinkrate_get filer_fcp_adapter_extgbic_get filer_fcp_adapter_fabric_get filer_fcp_adapter_field_get filer_fcp_adapter_fwrev_get filer_fcp_adapter_get filer_fcp_adapter_hostportaddr_get filer_fcp_adapter_initiators_get filer_fcp_adapter_is_online filer_fcp_adapter_is_up filer_fcp_adapter_linkbreaks_get filer_fcp_adapter_lipresets_get filer_fcp_adapter_mediatype_get filer_fcp_adapter_mediatype_set filer_fcp_adapter_offline filer_fcp_adapter_online filer_fcp_adapter_overruns_get filer_fcp_adapter_partneradapter_default filer_fcp_adapter_partneradapter_get filer_fcp_adapter_partneradapter_remove filer_fcp_adapter_partneradapter_set filer_fcp_adapter_pktsize_get filer_fcp_adapter_portname_get filer_fcp_adapter_requestsdropped_get filer_fcp_adapter_resets_get filer_fcp_adapter_slot_get filer_fcp_adapter_spuriousintr_get filer_fcp_adapter_sramparity_get filer_fcp_adapter_stats_check filer_fcp_adapter_stats_field_get filer_fcp_adapter_stats_get filer_fcp_adapter_stats_zero filer_fcp_adapter_status_get filer_fcp_adapter_totallogins_get filer_fcp_adapter_totallogouts_get filer_fcp_adapter_type_get filer_fcp_adapter_underruns_get filer_fcp_adapter_verbose_check filer_fcp_adapter_verbose_get filer_fcp_adapter_wwnn_get filer_fcp_adapters_check filer_fcp_adapters_get filer_fcp_adapters_slot_get filer_fcp_adapters_verbose_check filer_fcp_adapter_verbose_check filer_fcp_adapter_verbose_get filer_fcp_cfmode_get filer_fcp_cfmode_set filer_fcp_help filer_fcp_igroup_create filer_fcp_igroup_create_for_ostype filer_fcp_igroup_create_for_ostype_portset filer_fcp_initialize filer_fcp_initiator_check filer_fcp_initiators_check filer_fcp_is_installed filer_fcp_is_licensable filer_fcp_is_licensed filer_fcp_is_started filer_fcp_license filer_fcp_nodename_get filer_fcp_nodename_set filer_fcp_portset_create filer_fcp_start filer_fcp_stats_check filer_fcp_stats_zero filer_fcp_status_get filer_fcp_stop filer_fcp_trace_files_list filer_fcp_trace_start filer_fcp_trace_stop filer_fcp_unlicense filer_igroup_alua_get filer_igroup_alua_set filer_igroup_bind filer_igroup_destroy filer_igroup_help filer_igroup_is_defined filer_igroup_is_fcp filer_igroup_is_iscsi filer_igroup_lun_lunid_get filer_igroup_lun_show_get filer_igroup_lunid_lun_get filer_igroup_lunids_get filer_igroup_luns_get filer_igroup_node_add filer_igroup_node_remove filer_igroup_nodenames_get filer_igroup_os_get filer_igroup_os_set filer_igroup_rename filer_igroup_unbind filer_igroup_show filer_igroup_show_verbose filer_igroup_show_get filer_igroup_throttle_borrow_get filer_igroup_throttle_borrow_set filer_igroup_throttle_reserve_get filer_igroup_throttle_reserve_set filer_igroup_vaporize filer_igroup_verify_destroy_is_disabled filer_iscsi_alias_clear filer_iscsi_alias_get filer_iscsi_alias_set filer_iscsi_help filer_iscsi_igroup_create filer_iscsi_igroup_create_for_ostype filer_iscsi_initialize filer_iscsi_initiator_check filer_iscsi_interface_disable filer_iscsi_interface_enable filer_iscsi_interface_status filer_iscsi_interface_status_check filer_iscsi_is_installed filer_iscsi_is_licensable filer_iscsi_is_licensed filer_iscsi_is_started filer_iscsi_license filer_iscsi_nodename_get filer_iscsi_nodename_set filer_iscsi_portal_get filer_iscsi_security_default_get filer_iscsi_security_default_set filer_iscsi_security_generate_passwd filer_iscsi_security_initiator_auth_get filer_iscsi_security_initiator_add filer_iscsi_security_initiator_delete filer_iscsi_session_check filer_iscsi_session_show_output_parse filer_iscsi_session_verbose_check filer_iscsi_start filer_iscsi_stats_get filer_iscsi_stats_field_get filer_iscsi_stats_check filer_iscsi_stats_zero filer_iscsi_status_check filer_iscsi_status_get filer_iscsi_stop filer_iscsi_tpgroup_check filer_iscsi_tpgroup_create filer_iscsi_tpgroup_destroy filer_iscsi_tpgroup_get filer_iscsi_tpgroup_interface_add filer_iscsi_tpgroup_interface_remove filer_iscsi_tpgroup_exist filer_iscsi_tpgroup_name_get filer_iscsi_tpgroup_nic_available filer_iscsi_tpgroup_tag_get filer_iscsi_unlicense filer_isns_config filer_isns_get filer_isns_start filer_isns_stop filer_isns_update filer_lun_attribute_get filer_lun_clone filer_lun_clone_blockscomplete_get filer_lun_clone_percentcomplete_get filer_lun_clone_start filer_lun_clone_stop filer_lun_clone_wait filer_lun_comment_get filer_lun_comment_set filer_lun_convert filer_lun_convert_vld filer_lun_create filer_lun_clone_create filer_lun_clone_split_start filer_lun_clone_split_stop filer_lun_create_from_file filer_lun_create_from_file_with_noreserve filer_lun_create_from_snapshot filer_lun_create_from_snapshot_with_noreserve filer_lun_create_with_noreserve filer_lun_dbedit_clear filer_lun_dbedit_delete_by_index filer_lun_dbedit_delete_by_path filer_lun_dbedit_dump filer_lun_destroy filer_lun_df filer_lun_df_hex filer_lun_df_verbose filer_lun_exists filer_lun_geometry_get filer_lun_get filer_lun_help filer_lun_igroups_get filer_lun_is_mapped filer_lun_is_online filer_lun_map filer_lun_map_low filer_lun_maps_by_igroup_get filer_lun_maxsize_get filer_lun_maxsize_with_noreserve_get filer_lun_move filer_lun_offline filer_lun_online filer_lun_serial_get filer_lun_serial_set filer_lun_share filer_lun_share_all filer_lun_share_none filer_lun_share_read filer_lun_share_write filer_lun_show_get filer_lun_size_change filer_lun_size_decrease filer_lun_size_get filer_lun_size_increase filer_lun_stats_get filer_lun_stats_get_extended filer_lun_stats_zero filer_lun_subcommand_help filer_lun_unmap filer_lun_vaporize filer_luns_get filer_nodename_igroups_get filer_nodename_lunid_lun_get filer_nodename_lunid_vaporize filer_nodename_lunids_get filer_nodename_luns_get filer_portset_is_defined filer_portset_destroy filer_portset_destroy_force filer_portset_show filer_portset_show_get filer_portset_create filer_portset_add filer_portset_is_fcp filer_portset_port_remove filer_portset_port_add filer_vld_is_licensable filer_vld_is_licensed filer_vld_license filer_vld_unlicense filer_vol_snap_luns_vaporize iscsi_nodename_new arg_lun_size ); # Constants # Blocks Data ONTAP ZAPI Return codes use constant VDISK_ERROR_CLONE_NOT_SPLITTING2 => 9093; # observed with Anchorsteam.1 use constant VDISK_ERROR_CLONE_NOT_SPLITTING => 9048; use constant VDISK_ERROR_INITGROUP_MAPS_EXIST => 9029; use constant VDISK_ERROR_NO_SUCH_ATTRIBUTE => 9011; use constant VDISK_ERROR_NO_SUCH_INITGROUP => 9003; use constant VDISK_ERROR_NO_SUCH_VDISK => 9017; use constant VDISK_ERROR_NOT_QTREE_ROOT => 9001; # Define an upper bound on lun size, expecting a lun create to fail and reveal true amount that is available use constant lun_max_size_limit => 100000000000000; # 100 Terabytes # Field headings for the 'fcp stats' command use constant fcp_stats_field_adapter_read_ops => 'Read Ops'; use constant fcp_stats_field_adapter_write_ops => 'Write Ops'; use constant fcp_stats_field_adapter_other_ops => 'Other Ops'; use constant fcp_stats_field_adapter_kbytes_in => 'KBytes In'; use constant fcp_stats_field_adapter_kbytes_out => 'KBytes Out'; use constant fcp_stats_field_adapter_resets => 'Adapter Resets'; use constant fcp_stats_field_frame_overruns => 'Frame Overruns'; use constant fcp_stats_field_frame_underuns => 'Frame Underruns'; use constant fcp_stats_field_connected_initiators => 'Initiators Connected'; use constant fcp_stats_field_link_breaks => 'Link Breaks'; use constant fcp_stats_field_lip_resets => 'LIP Resets'; use constant fcp_stats_field_dropped_scsi_requests => 'SCSI Requests Dropped'; use constant fcp_stats_field_interrupts => 'Interrupts'; use constant fcp_stats_field_spurious_interrupts => 'Spurious Interrupts'; use constant fcp_stats_field_total_logins => 'Total Logins'; use constant fcp_stats_field_total_logouts => 'Total Logouts'; use constant fcp_stats_field_crc_errors => 'CRC Errors'; use constant fcp_stats_field_adapter_qfulls => 'Adapter Qfulls'; use constant fcp_stats_field_protocol_errors => 'Protocol Errors'; use constant fcp_stats_field_invalid_transmit_words => 'Invalid Transmit Words'; use constant fcp_stats_field_lr_sent => 'LR Sent'; use constant fcp_stats_field_lr_received => 'LR Received'; use constant fcp_stats_field_discarded_frames => 'Discarded Frames'; use constant fcp_stats_field_nos_received => 'NOS Received'; use constant fcp_stats_field_ols_received => 'OLS Received'; use constant fcp_stats_field_queue_depth => 'Queue Depth'; # Field headings for the 'fcp stats' command SFP section use constant fcp_stats_sfp_vendor_name => 'Vendor Name'; use constant fcp_stats_sfp_vendor_oui => 'Vendor OUI'; use constant fcp_stats_sfp_vendor_pn => 'Vendor PN'; use constant fcp_stats_sfp_vendor_rev => 'Vendor Rev'; use constant fcp_stats_sfp_serial_no => 'Serial No'; use constant fcp_stats_sfp_date_code => 'Date Code'; use constant fcp_stats_sfp_media_form => 'Media Form'; use constant fcp_stats_sfp_connector => 'Connector'; use constant fcp_stats_sfp_wavelength => 'Wavelength'; use constant fcp_stats_sfp_encoding => 'Encoding'; use constant fcp_stats_sfp_fc_speed_capabilities => 'FC Speed Capabilities'; # Field headings for the 'fcp stats' command vtic section use constant fcp_stats_vtic_1 => 'out_of_vtic_cmdblks'; use constant fcp_stats_vtic_2 => 'out_of_vtic_msgs'; use constant fcp_stats_vtic_3 => 'out_of_vtic_resp_msgs'; use constant fcp_stats_vtic_4 => 'out_of_bulk_msgs'; use constant fcp_stats_vtic_5 => 'out_of_bulk_buffers'; use constant fcp_stats_vtic_6 => 'out_of_r2t_buffers'; # Message headings for the 'fcp stats' command use constant fcp_stats_message_1 => 'VTIC_MESSAGE_L2P_NEW'; use constant fcp_stats_message_2 => 'VTIC_MESSAGE_L2P_DATA_IN'; use constant fcp_stats_message_3 => 'VTIC_MESSAGE_L2P_DATA_IN_RESP'; use constant fcp_stats_message_4 => 'VTIC_MESSAGE_L2P_RESP'; use constant fcp_stats_message_5 => 'VTIC_MESSAGE_L2P_DATA_OUT'; use constant fcp_stats_message_6 => 'VTIC_MESSAGE_L2P_ABORT'; use constant fcp_stats_message_7 => 'VTIC_MESSAGE_L2P_TASK_MGT'; use constant fcp_stats_message_8 => 'VTIC_MESSAGE_P2L_DATA_IN'; use constant fcp_stats_message_9 => 'VTIC_MESSAGE_P2L_DATA_IN_RESP'; use constant fcp_stats_message_10 => 'VTIC_MESSAGE_P2L_RESP'; use constant fcp_stats_message_11 => 'VTIC_MESSAGE_P2L_R2T'; use constant fcp_stats_message_12 => 'VTIC_MESSAGE_P2L_ABORT'; use constant fcp_stats_message_13 => 'VTIC_MESSAGE_FCP_CONFIG_INITIALIZE'; use constant fcp_stats_message_14 => 'VTIC_MESSAGE_FCP_CONFIG_UPDATE'; use constant fcp_stats_message_15 => 'VTIC_MESSAGE_L2P_ADD_INITIATOR'; use constant fcp_stats_message_16 => 'VTIC_MESSAGE_L2P_REMOVE_INITIATOR'; use constant fcp_stats_message_17 => 'VTIC_MESSAGE_L2P_GET_LUN_LIST'; use constant fcp_stats_message_18 => 'VTIC_MESSAGE_P2L_RETURN_LUN_LIST'; use constant fcp_stats_message_19 => 'VTIC_MESSAGE_DUMMY_UPDATE'; # Field headings for the 'fcp show adapter' command use constant fcp_adapter_field_slot => 'Slot'; use constant fcp_adapter_field_description => 'Description'; use constant fcp_adapter_field_fwrev => 'Firmware Rev'; use constant fcp_adapter_field_pci_bus_width => 'PCI Bus Width'; use constant fcp_adapter_field_pci_clock_speed => 'PCI Clock Speed'; use constant fcp_adapter_field_cacheline_size => 'Cacheline Size'; use constant fcp_adapter_field_fc_packet_size => 'FC Packet Size'; use constant fcp_adapter_field_sram_parity => 'SRAM Parity'; use constant fcp_adapter_field_external_gbic => 'External GBIC'; use constant fcp_adapter_field_data_link_rate => 'Data Link Rate'; use constant fcp_adapter_field_fabric_established => 'Fabric Established'; use constant fcp_adapter_field_connection_established => 'Connection Established'; use constant fcp_adapter_field_mediatype => 'Mediatype'; use constant fcp_adapter_field_partner_adapter => 'Partner Adapter'; use constant fcp_adapter_field_standby => 'Standby'; use constant fcp_adapter_field_target_port_id => 'Target Port ID'; # Fields for each physical adapter port (NOT present when virtual ports are displayed, see below) use constant fcp_adapter_name => 'Adapter Name'; use constant fcp_adapter_type => 'Adapter Type'; use constant fcp_adapter_field_status => 'Status'; use constant fcp_adapter_field_host_port_addr => 'Host Port Address'; # Spec'd with 'Host Port Addr' use constant fcp_adapter_field_fc_nodename => 'FC Nodename'; use constant fcp_adapter_field_fc_portname => 'FC Portname'; # New fields (with Scrimshaw) for each virtual adapter port use constant fcp_adapter_name_0 => 'Adapter Name_0'; use constant fcp_adapter_type_0 => 'Adapter Type_0'; # 'fcp show adapter fields that were moved out of the primary adapter area, for each adapter virtual port use constant fcp_adapter_field_status_0 => 'Status_0'; use constant fcp_adapter_field_host_port_addr_0 => 'Host Port Address_0'; # Spec'd with 'Host Port Addr' use constant fcp_adapter_field_fc_nodename_0 => 'FC Nodename_0'; use constant fcp_adapter_field_fc_portname_0 => 'FC Portname_0'; # 2nd virtual port (with Scrimshaw) use constant fcp_adapter_name_1 => 'Adapter Name_1'; use constant fcp_adapter_type_1 => 'Adapter Type_1'; use constant fcp_adapter_field_status_1 => 'Status_1'; use constant fcp_adapter_field_host_port_addr_1 => 'Host Port Address_1'; # Spec'd with 'Host Port Addr' use constant fcp_adapter_field_fc_nodename_1 => 'FC Nodename_1'; use constant fcp_adapter_field_fc_portname_1 => 'FC Portname_1'; # 3rd virtual port (with Scrimshaw) use constant fcp_adapter_name_2 => 'Adapter Name_2'; use constant fcp_adapter_type_2 => 'Adapter Type_2'; use constant fcp_adapter_field_status_2 => 'Status_2'; use constant fcp_adapter_field_host_port_addr_2 => 'Host Port Address_2'; # Spec'd with 'Host Port Addr' use constant fcp_adapter_field_fc_nodename_2 => 'FC Nodename_2'; use constant fcp_adapter_field_fc_portname_2 => 'FC Portname_2'; # Field headings for the 'fcp-adapter-list-info' zapi interface use constant fcp_adapter_zapi_field_slot => 'Slot'; use constant fcp_adapter_zapi_field_adapter => 'adapter'; use constant fcp_adapter_zapi_field_node_name => 'node-name'; use constant fcp_adapter_zapi_field_port_name => 'port-name'; use constant fcp_adapter_zapi_field_loop_id => 'loop-id'; use constant fcp_adapter_zapi_field_port_id => 'port-id'; use constant fcp_adapter_zapi_field_port_address => 'port-address'; use constant fcp_adapter_zapi_field_media_type => 'media-type'; use constant fcp_adapter_zapi_field_state => 'state'; use constant fcp_adapter_zapi_field_adapter_type => 'adapter-type'; use constant fcp_adapter_zapi_field_hardware_rev => 'hardware-rev'; use constant fcp_adapter_zapi_field_firmware_rev => 'firmware-rev'; use constant fcp_adapter_zapi_field_info_name => 'info-name'; use constant fcp_adapter_zapi_field_pc_bus_width => 'pc-bus-width'; use constant fcp_adapter_zapi_field_pc_clock_speed => 'pc-clock-speed'; use constant fcp_adapter_zapi_field_speed => 'speed'; use constant fcp_adapter_zapi_field_cache_line_size => 'cache-line-size'; use constant fcp_adapter_zapi_field_packet_size => 'packet-size'; use constant fcp_adapter_zapi_field_sram_parity => 'sram-parity-enabled'; use constant fcp_adapter_zapi_field_external_gbic => 'external-gbic-enabled'; use constant fcp_adapter_zapi_field_data_link_rate => 'data-link-rate'; use constant fcp_adapter_zapi_field_fabric_established => 'fabric-established'; use constant fcp_adapter_zapi_field_connection_established => 'connection-established'; use constant fcp_adapter_zapi_field_partner_adapter => 'partner-adapter'; use constant fcp_adapter_zapi_field_standby => 'standby'; # Field headings for the 'iscsi stats' command # NOTE: Total fields do not necessarily match those displayed # by the command; additional info added for uniqueness # # 'iSCSI PDUs Received' block: use constant iscsi_stats_field_scsi_cmds => 'SCSI-Cmd'; use constant iscsi_stats_field_nop_outs => 'Nop-Out'; use constant iscsi_stats_field_task_management_cmds => 'SCSI TaskMgtCmd'; use constant iscsi_stats_field_login_requests => 'LoginReq'; use constant iscsi_stats_field_logout_requests => 'LogoutReq'; use constant iscsi_stats_field_text_requests => 'Text Req'; use constant iscsi_stats_field_data_outs => 'DataOut'; use constant iscsi_stats_field_snacks => 'SNACK'; use constant iscsi_stats_field_unknowns => 'Unknown'; use constant iscsi_stats_field_total_received_pdus => 'Total PDUs Received'; # 'iSCSI PDUs Transmitted' block use constant iscsi_stats_field_scsi_responses => 'SCSI-Rsp'; use constant iscsi_stats_field_nop_ins => 'Nop-In'; use constant iscsi_stats_field_task_management_responses => 'SCSI TaskMgtRsp'; use constant iscsi_stats_field_login_responses => 'LoginRsp'; use constant iscsi_stats_field_logout_responses => 'LogoutRsp'; use constant iscsi_stats_field_text_responses => 'TextRsp'; use constant iscsi_stats_field_data_ins => 'Data_In'; use constant iscsi_stats_field_r2t => 'R2T'; use constant iscsi_stats_field_asyncmsgs => 'Asyncmsg'; use constant iscsi_stats_field_rejects => 'Reject'; use constant iscsi_stats_field_total_transmitted_pdus => 'Total PDUs Transmitted'; # 'iSCSI CDBs' block use constant iscsi_stats_field_data_in_blocks => 'DataIn Blocks'; use constant iscsi_stats_field_data_out_blocks => 'DataOut Blocks'; use constant iscsi_stats_field_cdb_errors => 'Error Status'; use constant iscsi_stats_field_cdb_successes => 'Success Status'; use constant iscsi_stats_field_total_cdbs => 'Total CDBs'; # 'iSCSI ERRORS' block use constant iscsi_stats_field_failed_logins => 'Failed Logins'; use constant iscsi_stats_field_failed_task_management_errors => 'Failed TaskMgt'; use constant iscsi_stats_field_failed_logouts => 'Failed Logouts'; use constant iscsi_stats_field_failed_text_cmds => 'Failed TextCmd'; use constant iscsi_stats_field_protocol_errors => 'Protocol'; use constant iscsi_stats_field_digest_errors => 'Digest'; use constant iscsi_stats_field_unexpected_session_disconnects => 'Unexpected session disconnects'; use constant iscsi_stats_field_outside_cmdsn_window_discards => 'PDU discards (outside CmdSN window)'; use constant iscsi_stats_field_invalid_header_discards => 'PDU discards (invalid header)'; use constant iscsi_stats_field_total_errors => 'Total Errors'; # Global variables # The output keywords of the 'fcp stats' command @filer_fcp_stats_fields = ( fcp_stats_field_adapter_read_ops, fcp_stats_field_adapter_write_ops, fcp_stats_field_adapter_other_ops, fcp_stats_field_adapter_kbytes_in, fcp_stats_field_adapter_kbytes_out, fcp_stats_field_adapter_resets, fcp_stats_field_frame_overruns, fcp_stats_field_frame_underuns, fcp_stats_field_connected_initiators, fcp_stats_field_link_breaks, fcp_stats_field_lip_resets, fcp_stats_field_dropped_scsi_requests, fcp_stats_field_interrupts, fcp_stats_field_spurious_interrupts, fcp_stats_field_total_logins, fcp_stats_field_total_logouts, fcp_stats_field_crc_errors, fcp_stats_field_adapter_qfulls, fcp_stats_field_protocol_errors, fcp_stats_field_invalid_transmit_words, fcp_stats_field_lr_sent, fcp_stats_field_lr_received, fcp_stats_field_discarded_frames, fcp_stats_field_nos_received, fcp_stats_field_ols_received, fcp_stats_field_queue_depth, fcp_stats_sfp_vendor_name, fcp_stats_sfp_vendor_oui, fcp_stats_sfp_vendor_pn, fcp_stats_sfp_vendor_rev, fcp_stats_sfp_serial_no, fcp_stats_sfp_date_code, fcp_stats_sfp_media_form, fcp_stats_sfp_connector, fcp_stats_sfp_wavelength, fcp_stats_sfp_encoding, fcp_stats_sfp_fc_speed_capabilities ); @filer_fcp_stats_vtic_fields = ( fcp_stats_field_adapter_read_ops, fcp_stats_field_adapter_write_ops, fcp_stats_field_adapter_other_ops, fcp_stats_field_adapter_kbytes_in, fcp_stats_field_adapter_kbytes_out, fcp_stats_vtic_1, fcp_stats_vtic_2, fcp_stats_vtic_3, fcp_stats_vtic_4, fcp_stats_vtic_5, fcp_stats_vtic_6 ); @filer_fcp_stats_message_fields = ( fcp_stats_message_1, fcp_stats_message_2, fcp_stats_message_3, fcp_stats_message_4, fcp_stats_message_5, fcp_stats_message_6, fcp_stats_message_7, fcp_stats_message_8, fcp_stats_message_9, fcp_stats_message_10, fcp_stats_message_11, fcp_stats_message_12, fcp_stats_message_13, fcp_stats_message_14, fcp_stats_message_15, fcp_stats_message_16, fcp_stats_message_17, fcp_stats_message_18, fcp_stats_message_19 ); # The output keywords of the 'fcp show adapter' command @filer_fcp_adapter_fields = ( fcp_adapter_field_slot, fcp_adapter_field_description, # New fields (with Scrimshaw) for each adapter port fcp_adapter_name_0, fcp_adapter_type_0, # 'fcp show adapter fields that were moved out of the primary adapter area, for each adapter port fcp_adapter_field_status_0, fcp_adapter_field_host_port_addr_0, # Spec'd with 'Host Port Addr' fcp_adapter_field_fc_nodename_0, fcp_adapter_field_fc_portname_0, # second adapter port fcp_adapter_name_1, fcp_adapter_type_1, fcp_adapter_field_status_1, fcp_adapter_field_host_port_addr_1, # Spec'd with 'Host Port Addr' fcp_adapter_field_fc_nodename_1, fcp_adapter_field_fc_portname_1, # third adapter port fcp_adapter_name_2, fcp_adapter_type_2, fcp_adapter_field_status_2, fcp_adapter_field_host_port_addr_2, # Spec'd with 'Host Port Addr' fcp_adapter_field_fc_nodename_2, fcp_adapter_field_fc_portname_2, ); # The output keywords of the 'fcp show adapter -v' command when virtual ports are present @filer_fcp_adapter_verbose_with_virtual_port_fields = ( fcp_adapter_field_slot, fcp_adapter_field_description, fcp_adapter_field_fwrev, fcp_adapter_field_pci_bus_width, fcp_adapter_field_pci_clock_speed, fcp_adapter_field_cacheline_size, fcp_adapter_field_fc_packet_size, fcp_adapter_field_sram_parity, fcp_adapter_field_external_gbic, fcp_adapter_field_data_link_rate, #fcp_adapter_field_connection_established, fcp_adapter_field_fabric_established, #fcp_adapter_field_mediatype, #fcp_adapter_field_partner_adapter, #fcp_adapter_field_standby, # New fields (with Scrimshaw) for each adapter port fcp_adapter_name_0, fcp_adapter_type_0, # 'fcp show adapter fields that were moved out of the primary adapter area, for each adapter port fcp_adapter_field_status_0, fcp_adapter_field_host_port_addr_0, # Spec'd with 'Host Port Addr' fcp_adapter_field_fc_nodename_0, fcp_adapter_field_fc_portname_0, # second adapter port fcp_adapter_name_1, fcp_adapter_type_1, fcp_adapter_field_status_1, fcp_adapter_field_host_port_addr_1, # Spec'd with 'Host Port Addr' fcp_adapter_field_fc_nodename_1, fcp_adapter_field_fc_portname_1, # third adapter port fcp_adapter_name_2, fcp_adapter_type_2, fcp_adapter_field_status_2, fcp_adapter_field_host_port_addr_2, # Spec'd with 'Host Port Addr' fcp_adapter_field_fc_nodename_2, fcp_adapter_field_fc_portname_2, ); # The output keywords of the 'fcp show adapter -v' command with NO virtual ports present @filer_fcp_adapter_verbose_fields = ( fcp_adapter_field_slot, fcp_adapter_field_description, fcp_adapter_field_status, fcp_adapter_field_host_port_addr, # Spec'd with 'Host Port Addr' fcp_adapter_field_fwrev, fcp_adapter_field_pci_bus_width, fcp_adapter_field_pci_clock_speed, fcp_adapter_field_fc_nodename, fcp_adapter_field_fc_portname, fcp_adapter_field_cacheline_size, fcp_adapter_field_fc_packet_size, fcp_adapter_field_sram_parity, fcp_adapter_field_external_gbic, fcp_adapter_field_data_link_rate, fcp_adapter_type, fcp_adapter_field_fabric_established, fcp_adapter_field_connection_established, fcp_adapter_field_mediatype, fcp_adapter_field_partner_adapter, fcp_adapter_field_standby, fcp_adapter_field_target_port_id, # Add in GB ); # The output keywords of the 'fcp-adapter-list-info' zapi interface when virtual ports are present @filer_fcp_adapter_verbose_with_virtual_port_zapi_fields = ( fcp_adapter_zapi_field_slot, fcp_adapter_zapi_field_adapter, fcp_adapter_zapi_field_node_name, fcp_adapter_zapi_field_port_name, fcp_adapter_zapi_field_loop_id, fcp_adapter_zapi_field_port_id, fcp_adapter_zapi_field_media_type, fcp_adapter_zapi_field_state, fcp_adapter_zapi_field_adapter_type, fcp_adapter_zapi_field_hardware_rev, fcp_adapter_zapi_field_firmware_rev, fcp_adapter_zapi_field_info_name, fcp_adapter_zapi_field_pc_bus_width, fcp_adapter_zapi_field_pc_clock_speed, fcp_adapter_zapi_field_speed, fcp_adapter_zapi_field_cache_line_size, fcp_adapter_zapi_field_packet_size, fcp_adapter_zapi_field_sram_parity, fcp_adapter_zapi_field_external_gbic, fcp_adapter_zapi_field_data_link_rate, fcp_adapter_zapi_field_connection_established, fcp_adapter_zapi_field_fabric_established, ); # The output keywords of the 'fcp-adapter-list-info' zapi interface @filer_fcp_adapter_verbose_zapi_fields = ( fcp_adapter_zapi_field_slot, fcp_adapter_zapi_field_adapter, fcp_adapter_zapi_field_node_name, fcp_adapter_zapi_field_port_name, fcp_adapter_zapi_field_port_address, fcp_adapter_zapi_field_adapter_type, fcp_adapter_zapi_field_media_type, fcp_adapter_zapi_field_state, fcp_adapter_zapi_field_standby, fcp_adapter_zapi_field_hardware_rev, fcp_adapter_zapi_field_firmware_rev, fcp_adapter_zapi_field_info_name, fcp_adapter_zapi_field_pc_bus_width, fcp_adapter_zapi_field_pc_clock_speed, fcp_adapter_zapi_field_speed, fcp_adapter_zapi_field_cache_line_size, fcp_adapter_zapi_field_packet_size, fcp_adapter_zapi_field_sram_parity, fcp_adapter_zapi_field_external_gbic, fcp_adapter_zapi_field_data_link_rate, fcp_adapter_zapi_field_connection_established, fcp_adapter_zapi_field_fabric_established, ); # The output keywords of the 'iscsi stats' command @filer_iscsi_stats_fields = ( # Field headings for the 'iscsi stats' command (iSCSI PDUs Received) iscsi_stats_field_scsi_cmds, iscsi_stats_field_nop_outs, iscsi_stats_field_task_management_cmds, iscsi_stats_field_login_requests, iscsi_stats_field_logout_requests, iscsi_stats_field_text_requests, iscsi_stats_field_data_outs, iscsi_stats_field_snacks, iscsi_stats_field_unknowns, iscsi_stats_field_total_received_pdus, # Field headings for the 'iscsi stats' command (iSCSI PDUs Transmitted) iscsi_stats_field_scsi_responses, iscsi_stats_field_nop_ins, iscsi_stats_field_task_management_responses, iscsi_stats_field_login_responses, iscsi_stats_field_logout_responses, iscsi_stats_field_text_responses, iscsi_stats_field_data_ins, iscsi_stats_field_r2t, iscsi_stats_field_asyncmsgs, iscsi_stats_field_rejects, iscsi_stats_field_total_transmitted_pdus, # Field headings for the 'iscsi stats' command (iSCSI CDBs) iscsi_stats_field_data_in_blocks, iscsi_stats_field_data_out_blocks, iscsi_stats_field_cdb_errors, iscsi_stats_field_cdb_successes, iscsi_stats_field_total_cdbs, # Field headings for the 'iscsi stats' command (iSCSI Errors) iscsi_stats_field_failed_logins, iscsi_stats_field_failed_logouts, iscsi_stats_field_failed_task_management_errors, iscsi_stats_field_failed_text_cmds, iscsi_stats_field_protocol_errors, iscsi_stats_field_digest_errors, # session disconnect mesg is deprecated in tsingtao. # iscsi_stats_field_unexpected_session_disconnects, iscsi_stats_field_outside_cmdsn_window_discards, iscsi_stats_field_invalid_header_discards, iscsi_stats_field_total_errors ); # The official ZAPI names of all LUN attributes my @lun_attribute_names = ( '@Anvil@ConvertedVLDName', '@Anvil@CreatedByVSS', '@Anvil@FSConsistent'. '@Anvil@RWVLDSnap', '@Anvil@SFSROnline', '@Anvil@SFSRStatus', '@Anvil@SnapMirSFSR', '@Anvil@VdiskTemp', 'clone', 'comment', 'custom', 'cylinder_size', 'enabled', 'extent_size', 'host_stamp', 'nvfail', 'old_size', 'path_last', 'pserial', 'select', 'serial', 'sfsr_clone_restore', 'share', 'snapshot', 'snapshot_path_last', 'type' ); param('FILER_TEST_UI','-default','CLI'); # Or 'ZAPI' # Internal variables my $filer_fcp_is_started; # Remember lun comment quoting rules that were observed. # =1 if quotes were used once # =2 if quotes were not used once # =3 if inconsistency has been reported my $lun_comment_with_whitespace_quote_rule = 0; my $lun_comment_without_whitespace_quote_rule = 0; # Local subroutines # Check that file arguments are valid sub arg_cfmode ($) { my $cfmode = shift; if (!defined($cfmode)) { ExitScriptError("The $cfmode parameter must be defined"); } if ($cfmode !~ m/^mixed$|^standby$|^partner$/) { ExitScriptError("The cfmode parameter must be: mixed|standby|partner"); } return $cfmode; } # Check that file arguments are valid # Add quotes if required. sub arg_file ($) { my $file = shift; if (!defined($file)) { ExitScriptError("The file parameter must be defined"); } if ($file =~ /\s|#|;|&/) { if ($file !~ /^'/) { $file = "'".$file."'"; } } return $file; } # Check that igroup name arguments are valid # Add quotes if required. sub arg_igroup ($) { my $igroup = shift; if (!defined($igroup)) { ExitScriptError("The igroup parameter must be defined"); } if ($igroup eq '') { ExitScriptError("The igroup parameter must not be empty"); } # ZAPI does not require quote (') between igroup return $igroup if ($main::FILER_TEST_UI =~ m/ZAPI/i); if ($igroup =~ /\s|#|;|&/) { if ($igroup !~ /^'/) { $igroup = "'".$igroup."'"; } } return $igroup; } # Check that lun_size arguments are valid # Allows undefined or empty string to imply current minimum size # return original and converted byte count sub arg_lun_size ($;$) { my $size = shift; my $ostype = arg_ostype(shift); my $min_size; if ($ostype =~ m/^solaris$/i) { $min_size = '4m'; } elsif ($ostype =~ m/^windows$/i) { # Although command reports 31.4m (32901120) is the minimum # 31400k does not work! # 31.4*1024 = 32153.6 but 32153k creates a LUN of 39.2m (41126400) # 32901120/1024 = 32130k which creates a LUN of 31.4m (32901120) $min_size = '32130k'; } elsif ($ostype =~ m/^vld$/i) { $min_size = '4m'; # Note vld is not supported on 'lun create -s' } elsif ($ostype =~ m/^image$/i) { $min_size = '4m'; } else { $min_size = ''; } if (defined($size)) { if ($size eq '') { if ($min_size eq '') { ExitScriptError("lun_size value must be specified"); } $size = $min_size; # The minimum lun size } } else { if ($min_size eq '') { ExitScriptError("lun_size value must be specified"); } $size = $min_size; # The minimum lun size } if ($size !~ /^\d+[c|w|b|k|m|g|t]?$/) { ExitScriptError("An invalid lun size was requested: $size"); } my $bytes = $size; $bytes =~ s/(\d+).*/$1/; if ($size !~ /^\d+$/) { my $multiplier = 1; # Handle codes at the end if ($size =~ /b/) { $multiplier = 512; } elsif ($size =~ /c/) { $multiplier = 1; } elsif ($size =~ /w/) { $multiplier = 2; } elsif ($size =~ /k/) { $multiplier = 1024; } elsif ($size =~ /m/) { $multiplier = 1024*1024; } elsif ($size =~ /g/) { $multiplier = 1024*1024*1024; } elsif ($size =~ /t/) { $multiplier = 1024*1024*1024*1024; } $bytes = $bytes * $multiplier; } if (wantarray) { return ($size,$bytes); } else { return $size; } } # Check that lunid name arguments are valid sub arg_lunid ($) { my $lunid = shift; if (!defined($lunid)) { ExitScriptError("The LUN id parameter must be defined"); } if ($lunid eq '') { ExitScriptError("The LUN id parameter must not be empty"); } if ($lunid !~ /^\d+$/) { ExitScriptError("The LUN id parameter must numeric"); } return $lunid; } # Check that igroup name arguments are valid # Add quotes if required. sub arg_nodename ($) { my $arg = shift; if (!defined($arg)) { ExitScriptError("The nodename parameter must be defined"); } if ($arg eq '') { ExitScriptError("The nodename parameter must not be empty"); } if ($arg =~ /\s|#|;|&/) { if ($arg !~ /^'/) { $arg = "'".$arg."'"; } } return $arg; } # Check that os_type arguments are valid sub arg_ostype ($) { my $ostype = shift; if (!defined($ostype ) || $ostype eq '') { $ostype = 'solaris'; } if ($ostype !~ m/^aix$|^hpux$|^image$|^linux$|^netware$|^solaris$|^vld$|^vmware$|^windows$/) { ExitScriptError("An unknown lun os_type value was specified: $ostype"); } return $ostype; } # Check that initiator group os_type arguments are valid sub arg_igroup_ostype ($) { my $ostype = shift; if (!defined($ostype) || $ostype eq '') { $ostype = 'solaris'; } if ($ostype !~ m/^aix$|^hpux$|^linux$|^netware$|^solaris$|^vld$|^vmware$|^windows$|^openvms$/) { ExitScriptError("An unknown igroup os_type value was specified: $ostype"); } return $ostype; } # Check that lun_path arguments are valid # Add quotes if required. sub arg_path ($) { my $path = shift; if (!defined($path)) { ExitScriptError("The lun_path parameter must be defined"); } if ($path !~ /^'*\/vol\//) { ExitScriptError("lun_path must start with '/vol/', script attempted: '$path'"); } # lun setup claims: Path cannot contain `'`, `"` , `&`, `#` or `;` if ($path =~ /\s|#|;|&|:|{|}/) { # Enclose in quotes if special characters or spaces are present if ($path !~ /^'/) { $path = "'".$path."'"; } } return $path; } # Check that lun_path arguments are valid # Add quotes if required. sub arg_snapshot_lun_path ($) { my $path = shift; if (!defined($path)) { ExitScriptError("The snapshot_lun_path parameter must be defined"); } if ($path !~ /^'*\/vol\/.*\/\.snapshot\//) { ExitScriptError("snapshot_lun_path must be in the form '/vol/your_volume/.snapshot/your_lun', script attempted: '$path'"); } if ($path =~ /\s|#|;|&/) { # Enclose in quotes if special characters or spaces are present if ($path !~ /^'/) { $path = "'".$path."'"; } } return $path; } # Check that lun_path arguments are valid # Add quotes if required. # Separate into three arguments for new command use sub arg_snapshot_lun_path2 ($) { my $path = shift; my $snapshot = ''; my $parent_lun = ''; if (!defined($path)) { ExitScriptError("The snapshot_lun_path parameter must be defined"); } if ($path =~ /^'*(\/vol\/[^\/]*\/)\.snapshot\/([^\/]*)\/(.*)/) { $path = "$1"; $snapshot = $2; $parent_lun = $3; print("snapshot path: $path, snapshot name: $snapshot\n"); } if ($path =~ /\s|#|;|&/) { # Enclose in quotes if special characters or spaces are present if ($path !~ /^'/) { $path = "'".$path."'"; } } if ($snapshot =~ /\s|#|;|&/) { # Enclose in quotes if special characters or spaces are present if ($snapshot !~ /^'/) { $snapshot = "'".$snapshot."'"; } } return ($path,$snapshot,$parent_lun); } # Check that a valid snap_name argument was passed # Add quotes if required. sub arg_snap_name ($) { my $parm = shift; if (!defined($parm)) { ExitScriptError("The snap_name parameter must be defined"); } if ($parm =~ /^'*\/vol\/|\.snapshot\//) { ExitScriptError("snap_name must not include /vol/ or .snapshot/, script attempted: '$parm'"); } if ($parm =~ /\s|#|;|&/) { # Enclose in quotes if special characters or spaces are present if ($parm !~ /^'/) { $parm = "'".$parm."'"; } } return $parm; } # Check that tpgroup name arguments are valid # Add quotes if required. sub arg_tpgroup ($) { my $tpgroup_name = shift; if (!defined($tpgroup_name)) { ExitScriptError("The tpgroup parameter must be defined"); } if ($tpgroup_name eq '') { ExitScriptError("The tpgroup parameter must not be empty"); } if ($tpgroup_name =~ m/default/) { ExitScriptError("Potential conflict tpgroup parameter. Must not use 'default' in parameter"); } if ($tpgroup_name =~ /\s|#|;|&/) { if ($tpgroup_name !~ /^'/) { $tpgroup_name = "'".$tpgroup_name."'"; } } return $tpgroup_name; } # Check that tptag name arguments are valid # number only between (1-256) sub arg_tpgtag ($) { my $tpgtag_value = shift; if (!defined($tpgtag_value)) { # tptag_value is optional. return undef if !defined return undef; } #logcomment("tpgtag_value=$tpgtag_value"); if ($tpgtag_value !~ m/\d+/) { ExitScriptError("tptag parameter must be numeric value"); } if ($tpgtag_value < 1 || $tpgtag_value > 256) { ExitScriptError("tptag parameter is out-of-range (1-256)"); } return $tpgtag_value; } sub lun_comment_check($) { my $comment = shift; my ($comment_start, $comment_end); $comment_start = $comment; $comment_start =~ s/^(.).*/$1/; $comment_end = $comment; $comment_end =~ s/.*(.)$/$1/; if ($comment_start eq '"' || $comment_start eq "'" || $comment_end eq '"' || $comment_end eq "'" ) { # A quote symbol was found at the beginning or end of the comment if ($comment_start ne $comment_end ) { ExitSoftError("Comment does not end with the same single or double quote character as it started with: $comment"); } else { if ($comment =~ /\s/) { # A quoted comment with whitespace has been found if ($lun_comment_with_whitespace_quote_rule == 0) { # Quoted comment with white space was found $lun_comment_with_whitespace_quote_rule = 1; } elsif ($lun_comment_with_whitespace_quote_rule == 2) { ExitSoftError("Inconsistent use of quotes around lun comment text with whitespace was detected"); $lun_comment_with_whitespace_quote_rule = 3; } } else { # A quoted comment without whitespace has been found if ($lun_comment_without_whitespace_quote_rule == 0) { $lun_comment_without_whitespace_quote_rule = 1; } elsif ($lun_comment_without_whitespace_quote_rule == 2) { ExitSoftError("Inconsistent use of quotes around lun comment text without whitespace was detected"); $lun_comment_without_whitespace_quote_rule = 3; } } } } else { # A comment without quotes has been found if ($comment =~ /\s/) { # An unquoted comment with whitespace has been found if ($lun_comment_with_whitespace_quote_rule == 0) { # Unquoted comment with white space was found $lun_comment_with_whitespace_quote_rule = 2; } elsif ($lun_comment_with_whitespace_quote_rule == 1) { ExitSoftError("Inconsistent use of quotes around lun comment text with whitespace was detected"); $lun_comment_with_whitespace_quote_rule = 3; } } else { # An unquoted comment without whitespace has been found if ($lun_comment_without_whitespace_quote_rule == 0) { $lun_comment_without_whitespace_quote_rule = 2; } elsif ($lun_comment_without_whitespace_quote_rule == 1) { ExitSoftError("Inconsistent use of quotes around lun comment text without whitespace was detected"); $lun_comment_without_whitespace_quote_rule = 3; } } } } # External subroutines: ## @name ::applies_to_filer_with_fcp_installed ## @summary Skips the current test case if filer does not have FCP ## @arg $fc Required: The filer connection object where this function is executed ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub applies_to_filer_with_fcp_installed ($;$) { my($fc, $prefix) = @_; my $filer = filer_name($fc); if (0 == @main::FILER_FCP_TARGET_NAMES) { ExitNA ("Filer $filer has no FILER_TARGET_NAMES with FCP protocol as required for this test."); } if (!filer_fcp_is_installed($fc, $prefix)) { ExitNA ("Filer $filer does not have FCP installed which is required for this test."); } } ## @name ::applies_to_filer_with_fcp_or_iscsi_installed ## @summary Skips the current test case if filer does not have FCP or iSCSI ## @arg $fc Required: The filer connection object where this function is executed ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub applies_to_filer_with_fcp_or_iscsi_installed ($;$) { my($fc, $prefix) = @_; my $filer = filer_name($fc); if (0 == @main::FILER_FCP_TARGET_NAMES && !defined($main::FILER_ISCSI_WWNN)) { ExitNA ("Filer $filer has neither an iSCSI node name nor FILER_TARGET_NAMES for the FCP protocol as required for this test."); } if ((!filer_fcp_is_installed($fc, $prefix)) && (!filer_iscsi_is_installed($fc, $prefix))) { ExitNA ("Filer $filer does not have FCP or iSCSI installed which is required for this test."); } } ## @name ::applies_to_filer_with_iscsi_installed ## @summary Skips the current test case if filer does not have FCP ## @arg $fc Required: The filer connection object where this function is executed ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub applies_to_filer_with_iscsi_installed ($;$) { my($fc, $prefix) = @_; my $filer = filer_name($fc); if (!defined($main::FILER_ISCSI_WWNN)) { ExitNA ("Filer $filer has no iSCSI node name as required for this test."); } if (!filer_iscsi_is_installed($fc, $prefix)) { ExitNA ("Filer $filer does not have iSCSI installed which is required for this test."); } } ## @name ::create_random_nodename ## @summary Creates one or more random FCP nodenames ## @arg $numNodeNames Optional: The number of nodenames requested, default is 1 sub create_random_nodename (;$) { my $numNodeNames = shift; my @nodeNames = (); $numNodeNames = 1 unless defined $numNodeNames; # nodename must be conformance # 50:0a:09:80:8X:XX:XX:XX for(my $n = 0; $n < $numNodeNames; $n++) { my @hexnumbers = (); @hexnumbers = split(/:/,"50:0a:09:80"); # fcp conformance push (@hexnumbers, int(rand(10)) + 80); # random between 80-89 my ($random, $number); for(my $x = 5; $x < 8; $x++) { $random = int(rand(255)) +1; $number = sprintf("%02x", $random); push(@hexnumbers, $number); } my $nodename = join(":", @hexnumbers); push(@nodeNames, $nodename); } if (wantarray) { return @nodeNames; } else { return $nodeNames[0]; } } ## @name ::fcp_nodename_new ## @summary Creates one or more random FCP nodenames ## @arg $numNodeNames Optional: The number of nodenames requested, default is 1 sub fcp_nodename_new (;$) { my $numNodeNames = shift; my @nodeNames = (); $numNodeNames = 1 unless defined $numNodeNames; for(my $n = 0; $n < $numNodeNames; $n++) { my @hexnumbers = (); my ($random, $number); for(my $x = 0; $x < 8; $x++) { $random = int(rand(255)) +1; $number = sprintf("%02x", $random); push(@hexnumbers, $number); } my $nodename = join(":", @hexnumbers); push(@nodeNames, $nodename); } if (wantarray) { return @nodeNames; } else { return $nodeNames[0]; } } ## @name ::filer_command_help ## @summary Show help text for command subcommands ## @arg $fc Required: The filer connection object where this function is executed ## @arg $command Required: The command to check help on ## @arg $subcommand Required: The command subcommand to check help on ## @arg @expected a list of expected values each passed as a regular expression sub filer_command_help ($$$;@) { my($fc) = shift; my $command = shift; my($subcommand) = shift; my(@expected) = @_; my $cmd = "$command help $subcommand"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/$command: help not found/) { if ($cmd =~ m/vfiler run/ ) { ExitSoftError('-burt',104271,"$command help was not recognized in a vfiler context"); } else { ExitSoftError("$command help was not recognized outside of a vfiler context"); } } else { my $numLinesMatch = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ( ($line =~ /$command $subcommand/) || ($line =~ / - \w+/) ){ $numLinesMatch++; } } my @notseen = (); foreach my $expected (@expected) { if (0 == grep { /$expected/ } @info) { push(@notseen,$expected); } } if (@notseen >0) { my $not_seen = join("\n",@notseen); ExitSoftError("Missing expected help text output: '$not_seen'"); } ExitSoftError("Help information not displayed:", @info) if ($numLinesMatch < 1); } } ## @name ::filer_fcp_adapter_buswidth_get ## @summary Returns the FCP PCI Bus Width ## @description ## Returns the FCP PCI Bus Width or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_buswidth_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_pci_bus_width); } ## @name ::filer_fcp_adapter_cachesize_get ## @summary Returns the FCP Cacheline Size ## @description ## Returns the FCP Cacheline Size an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_cachesize_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_cacheline_size); } ## @name ::filer_fcp_adapter_check ## @summary Checks generic expected output of the 'fcp show adapter $adapter' command. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_check ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp show adapter $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $return = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /Standby:\s+No/) { $return = 1; } if ($line =~ /Standby:\s+Yes/) { $return = 2; } } return $return; } ## @name ::filer_fcp_adapter_clockspeed_get ## @summary Returns the FCP PCI Clock Speed ## @description ## Returns the FCP PCI Clock Speed or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_clockspeed_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_pci_clock_speed); } ## @name ::filer_fcp_adapter_crcerrs_get ## @summary Returns the number of FCP CRC Errors ## @description ## Returns the number of FCP CRC Errors or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_crcerrs_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_crc_errors); } ## @name ::filer_fcp_adapter_desc_get ## @summary Returns the FCP Description ## @description ## Returns the FCP Description or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_desc_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_description); } ## @name ::filer_fcp_adapter_dlinkrate_get ## @summary Returns the FCP Data Link Rate ## @description ## Returns the FCP Data Link Rate and empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_dlinkrate_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_data_link_rate); } ## @name ::filer_fcp_adapter_extgbic_get ## @summary Returns the FCP External GBIC value ## @description ## Returns the FCP External GBIC value and empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_extgbic_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_external_gbic); } ## @name ::filer_fcp_adapter_fabric_get ## @summary Returns the FCP Fabric Established value ## @description ## Returns the FCP Fabric Established value and empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_fabric_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_fabric_established); } ## @name ::filer_fcp_adapter_field_get ## @summary Returns the statistics for the field requested per adapter ## @description ## Returns the FCP adapter value or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name ## @arg $field Required: The text that identifies the field (on the fcp show command output). sub filer_fcp_adapter_field_get ($$$) { my $fc = shift; my $adapter = shift; my $field = shift; # Some configurations do not have virtual ports # They will not see the virtual port suffixed filed names my $only_field = $field; if ($only_field =~ m/_0$/) { $only_field =~ s/_0$//; } my %stats = filer_fcp_adapter_get($fc,$adapter); if (exists($stats{$field}) ) { return $stats{$field}; } elsif (exists($stats{$only_field})) { return $stats{$only_field}; } else { ExitSoftError("No value found for fcp adapter '$field' field."); return undef; } } ## @name ::filer_fcp_adapter_fwrev_get ## @summary Returns the FCP Firmware Rev ## @description ## Returns the FCP Firmware Rev or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_fwrev_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_fwrev); } ## @name ::filer_fcp_adapter_get ## @summary Returns a hash of fcp adapter fields and values for the requested adapter ## @description ## Returns a hash table of all FCP adapter values found from the 'fcp show adapter -v' command ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_get ($$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A non-empty adapter name parameter must be passed"); } my %hash = (); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-adapter-list-info", "fcp-adapter",$adapter); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # check for media-type my $media_type = &ZapiUtil::zapi_parse_output($text, "media-type"); # get adapter info my %HoH = (); if ($media_type =~ /(ptp|auto)/) { %HoH = &ZapiUtil::zapi_parse_output($text, "adapter", "node-name","port-name","port-address","adapter-type", "media-type","state","standby"); } elsif ($media_type eq 'loop') { # loop media-type %HoH = &ZapiUtil::zapi_parse_output($text, "adapter", "node-name","port-name","loop-id","port-id", "media-type","state","adapter-type"); } else { # auto media-type ExitError("media-type=$media_type have not implement for command '$cmd'"); } # put into hash to be compatible w/ CLI output # should only be one adapter, use 1st adapter and set flag to exit my $adapt_num = 0; foreach my $adapt (keys %HoH) { $hash{'adapter'} = $adapt; if ($adapt_num > 0) { next; } # zapi does not provide slot, work around parsing adapter if ($adapt =~ m/.*_\d+$/) { ($hash{'Slot'},undef) = split("_",$adapt); } else { # Slot is adapter $hash{'Slot'} = $adapt; } foreach my $key (keys %{$HoH{$adapt}} ) { $hash{$key} = $HoH{$adapt}{$key}; } $adapt_num++; } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "fcp show adapter -v $adapter"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); my $port = ''; foreach my $line (@info) { if ($line !~ /:/) {next} my ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; if ($key eq "Adapter Name" ) { # Note each port has its own section if ($line =~ m/.*_\S+\s*/) { (undef,$port) = split("_",$line); } } if ($port eq '') { $hash{$key} = $value; } else { $hash{$key."_$port"} = $value; } } } else { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } return %hash; } ## @name ::filer_fcp_adapter_hostportaddr_get ## @summary Returns the FCP Host Port Address ## @description ## Returns the FCP Host Port Address or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_hostportaddr_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_host_port_addr_0); } ## @name ::filer_fcp_adapter_initiators_get ## @summary Returns the number of FCP Initiators Connected ## @description ## Returns the number of FCP Initiators Connected or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_initiators_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_connected_initiators); } ## @name ::filer_fcp_adapter_interrupt_get ## @summary Returns the number of FCP Interrupts ## @description ## Returns the number of FCP Interrupts or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_interrupt_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_interrupts); } ## @name ::filer_fcp_adapter_is_online ## @summary Determines if an fcp adapter is online. ## Returns 1 if online, otherwise 0. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_is_online ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp config $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Return code ($result) indicates command did not succeed '$cmd'"); } my $return = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /\s+ONLINE\s+/) { $return = 1; } } return $return; } ## @name ::filer_fcp_adapter_is_up ## @summary Determines if an fcp adapter is up. ## Returns 1 if up, otherwise 0. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_is_up ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp config $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Return code ($result) indicates command did not succeed '$cmd'"); } my $return = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ //) { $return = 1; } } return $return; } ## @name ::filer_fcp_adapter_linkbreaks_get ## @summary Returns the number of FCP Link Breaks ## @description ## Returns the number of FCP Link Breaks or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_linkbreaks_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_link_breaks); } ## @name ::filer_fcp_adapter_lipresets_get ## @summary Returns the number of FCP LIP Resets ## @description ## Returns the number of FCP LIP Resets or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_lipresets_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_lip_resets); } ## @name ::filer_fcp_adapter_mediatype_get ## @summary Returns the FCP Mediatype ## @description ## Returns the FCP Mediatype or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_mediatype_get ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp config $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $mediatype = ''; if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); foreach my $line (@info) { if ($line !~ /mediatype /) {next} $line =~ s/.* mediatype (\w+).*/$1/; $mediatype = $line; last; } } else { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } return $mediatype; } ## @name ::filer_fcp_adapter_mediatype_set ## @summary Set the FCP Partner Adapter value ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name ## @arg $mediatype Required: The fcp adapter's mediatype ('auto', 'loop', or 'ptp') sub filer_fcp_adapter_mediatype_set ($$$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A non-empty adapter name parameter must be passed"); } my $mediatype = shift; if ($mediatype eq '') { ExitScriptError("A non-empty mediatype parameter must be passed"); } my $cmd = "fcp config $adapter mediatype $mediatype"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($mediatype ne filer_fcp_adapter_mediatype_get($fc,$adapter)) { ExitError("Failed to set FCP adapter $adapter mediatype to $mediatype"); } else { logcomment("FCP adpater $adapter already has mediatype $mediatype"); } } } ## @name ::filer_fcp_adapter_offline ## @summary Takes an fcp adapter offline. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_offline ($$) { my $fc = shift; my $adapter = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-adapter-config-down", "fcp-adapter",$adapter); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), @api_args); } else { # Use ONTAP CLI $cmd = "fcp config $adapter down"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { if (filer_fcp_adapter_is_online($fc,$adapter)) { ExitError("Failed to set FCP adapter $adapter offline"); } else { logcomment("FCP adapter $adapter is already offline"); } } } ## @name ::filer_fcp_adapter_online ## @summary Brings an fcp adapter online. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_online ($$) { my $fc = shift; my $adapter = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-adapter-config-up", "fcp-adapter",$adapter); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), @api_args); } else { # Use ONTAP CLI $cmd = "fcp config $adapter up"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { if (!filer_fcp_adapter_is_online($fc,$adapter)) { if (!filer_fcp_adapter_is_up($fc,$adapter)) { ExitError("Failed to set FCP adapter $adapter online"); } } else { logcomment("FCP adapter $adapter is already online"); } } } ## @name ::filer_fcp_adapter_overuns_get ## @summary Returns the number of FCP Frame Overruns ## @description ## Returns the number of FCP Frame Overruns or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_overruns_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_frame_overruns); } ## @name ::filer_fcp_adapter_partneradapter_default ## @summary Replaces the current FCP Partner Adapter value with the cfmode dependent default ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_partneradapter_default ($$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A non-empty adapter name parameter must be passed"); } my $cmd = "fcp config $adapter -partner"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); my $partner = filer_fcp_adapter_partneradapter_get($fc, $adapter); # Verify the right cfmode dependent default was assigned my $cfmode = filer_fcp_cfmode_get($fc); if ($cfmode =~ m/^mixed$/) { } elsif ($cfmode =~ m/^dual_fabric$/) { } elsif ($cfmode =~ m/^partner$/) { } elsif ($cfmode =~ m/^standby$/) { } elsif ($cfmode =~ m/^single_image$/) { } else { ExitScriptError("Unrecognized fcp cfmode '$cfmode'"); } # What are the right default values??? if ($partner !~ m/None/i) { #ExitError("Failed to set default FCP adapter $adapter partner to $right_default"); } } } ## @name ::filer_fcp_adapter_partneradapter_get ## @summary Returns the FCP Partner Adapter value ## @description ## Returns the FCP Partner Adapter value or empty string if none is found ## or the partner adapter could not be determined. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_partneradapter_get ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp config $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $partneradapter = ''; if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); foreach my $line (@info) { # 5b: WAITING FOR LINK UP # host address 000000 # portname 00:01:02:07:04:05:06:07 nodename 00:01:02:03:04:05:06:07 # mediatype loop partner adapter 4a if ($line =~ /partner adapter/) { if ($line =~ /partner adapter\s+\w+/) { $line =~ s/.*\s+partner adapter\s+(\w+)(\s|$).*/$1/s; $partneradapter = $line; } else { ExitSoftError('-burt',74551,"No partner adapter value was displayed by '$cmd'"); $partneradapter = 'None'; # fake out response to cover for this burt } last; } } if ($partneradapter eq '') { ExitSoftError("Partner adapter was not displayed by '$cmd'"); } } else { ExitSoftError("FCP partner adapter could not be determined, unexpected return code ($result) from '$cmd'"); } if ($partneradapter eq 'None') { $partneradapter = ''; } return $partneradapter; } ## @name ::filer_fcp_adapter_partneradapter_remove ## @summary Removes the FCP Partner Adapter value ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_partneradapter_remove ($$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A non-empty adapter name parameter must be passed"); } my $cmd = "fcp config $adapter partner None"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { # What value do we need to test to verify we still have the right default??? ExitError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_fcp_adapter_partneradapter_set ## @summary Set the FCP Partner Adapter value ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name ## @arg $partner_adapter Required: The fcp partner's target adapter name sub filer_fcp_adapter_partneradapter_set ($$$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A non-empty adapter name parameter must be passed"); } my $partner_adapter = shift; if ($partner_adapter eq '') { ExitScriptError("A non-empty partner adapter name parameter must be passed"); } my $cmd = "fcp config $adapter partner $partner_adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($partner_adapter eq '0c' and $text =~ m/0c is not a valid adapter/) { # 108949 Cannot assign 0c as an FCP partner adapter on Jivaros systems ExitSoftError('-burt',108949,"Could not set 0c as a partner adapter from '$cmd'"); # Recover from this filer_fcp_adapter_partneradapter_default($fc,$adapter); } else { ExitSoftError("Unexpected return code ($result) from '$cmd'"); my $current_partner_adapter = filer_fcp_adapter_partneradapter_get($fc, $adapter); if ($partner_adapter ne $current_partner_adapter) { ExitError("Failed to set FCP adapter $adapter partner adapter to $partner_adapter"); } } } else { if ($text =~ m/may not be a valid partner adapter/) { # This message has appeared for valid adapters # As this function is only to be called to assign a valid partner adapter, # this message is not appropriate if it is seen here ExitSoftError('-burt',103775,"Possible invalid filer warning from '$cmd'"); } } } ## @name ::filer_fcp_adapter_pktsize_get ## @summary Returns the FCP Packet Size ## @description ## Returns the FCP Packet Size an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name. sub filer_fcp_adapter_pktsize_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_fc_packet_size); } ## @name ::filer_fcp_adapter_portname_get ## @summary Returns the FCP Portname ## @description ## Returns the FCP Portname or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_portname_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_fc_portname_0); } ## @name ::filer_fcp_adapter_requestsdropped_get ## @summary Returns the number of FCP SCSI Requests Dropped ## @description ## Returns the number of FCP SCSI Requests Dropped or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_requestsdropped_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_dropped_scsi_requests); } ## @name ::filer_fcp_adapter_resets_get ## @summary Returns the number of FCP Adapter Resets ## @description ## Returns the number of FCP Adapter Resets or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_resets_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_adapter_resets); } ## @name ::filer_fcp_adapter_slot_get ## @summary Returns the FCP Slot name ## @description ## Returns the FCP node name or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_slot_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_slot); } ## @name ::filer_fcp_adapter_spuriousintr_get ## @summary Returns the number of FCP Spurious Interrupts @description ## Returns the number of FCP Spurious Interrupts or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_spuriousintr_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_spurious_interrupts); } ## @name ::filer_fcp_adapter_sramparity_get ## @summary Returns the FCP SRAM Parity value ## @description ## Returns the FCP SRAM Parity value and empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_sramparity_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_sram_parity); } ## @name ::filer_fcp_adapter_stats_check ## @summary Verifies all information is supplied for ## the FILER_TARGETn_NAME given. ## @description ## A hash of stats for the specified adapter is returned. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_stats_check ($$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A valid adapter name is required"); } my %adapter_info = filer_fcp_adapter_stats_get($fc, $adapter); my %totalstats; my $linesOfOutput=0; no strict 'refs'; for my $info ( keys %adapter_info) { my $keyword = ''; for my $akeyword (@filer_fcp_stats_fields) { if ($akeyword =~ /$info/i ) { $keyword = $akeyword; if ($info ne $keyword) { ExitSoftError("Expected keyword '$keyword', got '$info'"); } last; } } if ($keyword eq '') { ExitSoftError("Unknown keyword '$info' from 'fcp stats', did specifications change?"); } else { $linesOfOutput++; $totalstats{$keyword}+=$adapter_info{$info}; } use strict 'refs'; } # # should have one line output for each statisic for each adapter # if ($linesOfOutput != @filer_fcp_stats_fields) { # ExitSoftError( "Not enough lines of output ($linesOfOutput)."); # } # verify expected count of lines of output my $expected_field_count = @filer_fcp_stats_fields; # Check if adapter is 2Gb or not, if yes the expected field count will be 11 less my $command = "fcp show adapter -v $adapter"; my($text,$result) = filer_cli_say($fc,$command,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /Description:(.*)QLogic 2322(.*)/i) { $expected_field_count = $expected_field_count - 11; #2Gb adapter do not have SFP information } } if ($linesOfOutput != $expected_field_count) { if ($expected_field_count<$linesOfOutput) { ExitSoftError("Duplicate field(s) reported"); } else { my $missing = $expected_field_count-$linesOfOutput; ExitSoftError("Missing some expected output field(s)"); } } return %totalstats; } ## @name ::filer_fcp_adapter_stats_field_get ## @summary Returns a specific FCP statistics value for the specifed adapter. ## @description ## Returns the FCP adapter stats value or undefined. ## An error message is reported for undefined fields. ## ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name ## @arg $adapter Required: The text to identify the requested field. sub filer_fcp_adapter_stats_field_get ($$$) { my $fc = shift; my $adapter = shift; my $field = shift; my %stats = filer_fcp_adapter_stats_get($fc,$adapter); if (exists($stats{$field}) ) { return $stats{$field}; } else { ExitSoftError("No value found for fcp stats '$field'."); return undef; } } ## @name ::filer_fcp_adapter_stats_get ## @summary Returns the FCP statistics for FCP adapters. ## @description ## Returns a hash of FCP adapter stats values. ## If no adapter is specified, it sums the values across all FCP adapters. ## ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Optional: The fcp target adapter name. sub filer_fcp_adapter_stats_get ($$) { my $fc = shift; my $adapter = shift; if (!defined($adapter)) { $adapter = ''; } my $cmd = "fcp stats $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my %stats = (); my $counter=0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line !~ /:/) {next} my ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; if (defined($stats{$key})) { $stats{$key} += $value; } else { $stats{$key} = $value; } } return %stats; } ## @name ::filer_fcp_adapter_stats_zero ## @summary Zeroes the FCP statistics for the specifed adapter ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_stats_zero ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp stats -z $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_fcp_adapter_status_get ## @summary Returns the FCP Status ## @description ## Returns the FCP Status or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_status_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_status_0); } ## @name ::filer_fcp_adapter_totallogins_get ## @summary Returns the number of FCP Total Logins ## @description ## Returns the number of FCP Total Logins or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_totallogins_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_total_logins); } ## @name ::filer_fcp_adapter_totallogouts_get ## @summary Returns the number of FCP Total Logouts ## @description ## Returns the number of FCP Total Logouts or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_totallogouts_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_total_logouts); } ## @name ::filer_fcp_adapter_type_get ## @summary Returns the FCP adapter type ## @description ## Returns the FCP Status or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_type_get ($$) { my $fc = shift; my $adapter = shift; if ($main::FILER_TEST_UI =~ m/ZAPI/i) { return filer_fcp_adapter_field_get($fc, $adapter,"adapter" ); } else { # ONTAP CLI return filer_fcp_adapter_field_get($fc, $adapter,fcp_adapter_type_0 ); } } ## @name ::filer_fcp_adapter_underruns_get ## @summary Returns the number of FCP Frame Underruns ## @description ## Returns the number of FCP Frame Underruns or undef if not found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_underruns_get ($$) { my $fc = shift; my $adapter = shift; return filer_fcp_adapter_stats_field_get($fc, $adapter, fcp_stats_field_frame_underuns); } ## @name ::filer_fcp_adapter_verbose_check ## @summary Checks generic expected output of the 'fcp show adapter -v $adapter' command. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_verbose_check ($$) { my $fc = shift; my $adapter = shift; my $cmd = "fcp show adapter -v $adapter"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $return = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /Slot:\s+$adapter/) { # Now check all expected keyword fields my %adapter_info = filer_fcp_adapter_verbose_get($fc,$adapter); # Determine the type of output format to expect my @expected_fields; if ($main::FILER_TEST_UI =~ m/ZAPI/i) { if ($adapter_info{&fcp_adapter_zapi_field_adapter} =~ m/.*_\d+$/) { # A virtual port definition was seen @expected_fields = @filer_fcp_adapter_verbose_with_virtual_port_zapi_fields; print("ZAPI - FCP virtual ports detected\n"); } else { @expected_fields = @filer_fcp_adapter_verbose_zapi_fields; print("ZAPI - No FCP virtual ports detected\n"); } } else { # Use ZAPI CLI if (defined($adapter_info{&fcp_adapter_name_0})) { @expected_fields = @filer_fcp_adapter_verbose_with_virtual_port_fields; print("FCP virtual ports detected\n"); } else { # Use ONTAP CLI @expected_fields = @filer_fcp_adapter_verbose_fields; print("No FCP virtual ports detected\n"); } } my $linesOfOutput=0; no strict 'refs'; for my $info ( keys %adapter_info) { my $found = 0; for my $akeyword (@expected_fields) { if ($akeyword =~ /^$info$/i ) { # logcomment("info: $info, akeyword: $akeyword"); $linesOfOutput++; $found++; last; } } if (not $found) { ExitSoftError("Unknown keyword '$info' from '$cmd', did specifications change?"); } } use strict 'refs'; # Verify expected count of lines of output my $expected_field_count; $expected_field_count = @expected_fields; if ($linesOfOutput != $expected_field_count) { if ($expected_field_count<$linesOfOutput) { ExitSoftError("Duplicate field(s) reported by 'fcp show adapter -v $adapter'") } else { my $missing = $expected_field_count-$linesOfOutput; ExitSoftError("Missing (".$missing.") of ".$expected_field_count." expected output field(s) of 'fcp show adapter -v $adapter'") } } $return = 1; } if ($line =~ /Adapter $adapter is running on behalf of the partner/) { $return = 2; } } return $return; } ## @name ::filer_fcp_adapter_verbose_get ## @summary Returns a hash of fcp adapter fields and values for the requested adapter ## @description ## Returns all FCP adapter values or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: fcp target adapter name sub filer_fcp_adapter_verbose_get ($$) { my $fc = shift; my $adapter = shift; if ($adapter eq '') { ExitScriptError("A non-empty adapter name parameter must be passed"); } my $adapter_name = shift; my %hash; my ($key, $value); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate( $fc)) { my @api_args = ("fcp-adapter-list-info", "fcp-adapter",$adapter, "verbose","true"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # check for media-type # my $media_type = &ZapiUtil::zapi_parse_output($text, "media-type"); # get adapter info # my %HoH = (); # if ($media_type eq 'ptp') { # %HoH = &ZapiUtil::zapi_parse_output($text, "adapter", # "node-name","port-name","port-address","adapter-type", # "media-type","partner-adapter","state","standby"); # } elsif ($media_type eq 'loop') { # loop media-type # %HoH = &ZapiUtil::zapi_parse_output($text, "adapter", # "node-name","port-name","loop-id","port-id", # "media-type","state","adapter-type"); # } else { # auto media-type # ExitError("media-type=$media_type have not implement for command '$cmd'"); # } # put into hash to be compatible w/ CLI output # should only be one adapter, use 1st adapter and set flag to exit my $adapt_num = 0; my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; #logcomment("key: $key, value: $value"); if($adapt_num > 1) { next; } $hash{$key} = $value; if ($key eq 'adapter') { $adapt_num++; # zapi does not provide slot, work around parsing adapter if ($value =~ m/.*_\d+$/) { ($hash{'Slot'},undef) = split("_",$value); } else { # Slot is adapter $hash{'Slot'} = $value; } } } } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "fcp show adapter -v $adapter"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); my $port = ''; foreach my $line (@info) { if ($line !~ /:/) {next} my ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; $value =~ s/\s+$//; if ($key eq "Adapter Name" ) { # When this field appears, each virtual port has its own section if ($value =~ m/.*_\d+$/) { (undef,$port) = split("_",$value); } } if ($port eq '') { $hash{$key} = $value; } else { $hash{$key."_$port"} = $value; } } } else { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } return %hash; } ## @name ::filer_fcp_adapter_wwnn_get ## @summary Returns the FCP Nodename ## @description ## Returns the FCP node name or an empty string if none is found. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name sub filer_fcp_adapter_wwnn_get ($$) { my $fc = shift; my $adapter = shift; if ($main::FILER_TEST_UI =~ m/ZAPI/i) { return filer_fcp_adapter_field_get($fc, $adapter, "node-name"); } else { # ONTAP CLI return filer_fcp_adapter_field_get($fc, $adapter, fcp_adapter_field_fc_nodename_0); } } ## @name ::filer_fcp_adapters_check ## @summary Verifies all information is displayed ## by the 'fcp show adapter' command for all fcp target adapters. ## @description ## Examines the 'fcp show adapter' output. ## Verifies each field is displayed for each adapter. ## @arg $fc Required: The filer connection object where this function is executed ## @arg @adapter Optional: All fcp target adapter names sub filer_fcp_adapters_check ($;@) { my $fc = shift; my @adapters = @_; my $adaptercount; my @adapter_info = filer_fcp_adapters_get($fc); if (@adapters) { $adaptercount = @adapters; } else { $adaptercount = scalar(@adapter_info); logcomment("Found $adaptercount FCP adapters on this filer."); } my $cmd = (); if ($main::FILER_TEST_UI =~ m/ZAPI/i) { $cmd = 'fcp-adapter-list-info'; } else { # Use ONTAP CLI $cmd = 'fcp show adapter -v'; } my $linesOfOutput=0; # Determine the type of output format to expect my @expected_fields; for my $now_adapter (@adapter_info) { no strict 'refs'; if ($main::FILER_TEST_UI =~ m/ZAPI/i) { if (${$now_adapter}{&fcp_adapter_zapi_field_adapter} =~ m/.*_\d+$/) { # A virtual port definition was seen @expected_fields = @filer_fcp_adapter_verbose_with_virtual_port_zapi_fields; print("ZAPI - FCP virtual ports detected\n"); } else { @expected_fields = @filer_fcp_adapter_verbose_zapi_fields; print("ZAPI - No FCP virtual ports detected\n"); } } else { # Use ZAPI CLI if (defined(${$now_adapter}{&fcp_adapter_name_0})) { @expected_fields = @filer_fcp_adapter_verbose_with_virtual_port_fields; print("FCP virtual ports detected\n"); } else { @expected_fields = @filer_fcp_adapter_verbose_fields; print("No FCP virtual ports detected\n"); } } for my $info ( keys %$now_adapter) { my $keyword = ''; for my $akeyword (@expected_fields) { if ($akeyword =~ /^$info$/i ) { $keyword = $akeyword; if ($info ne $keyword) { ExitSoftError("Expected keyword '$keyword', got '$info'"); } last; } } if ($keyword eq '') { ExitSoftError("Unknown keyword '$info' from '$cmd', did specifications change?"); } else { $linesOfOutput++; } } use strict 'refs'; } # verify expected count of lines of output my $expected_field_count = @expected_fields*$adaptercount; if ($linesOfOutput != $expected_field_count) { if ($expected_field_count<$linesOfOutput) { ExitSoftError("Duplicate field(s) reported by '$cmd'") } else { my $missing = $expected_field_count-$linesOfOutput; ExitSoftError("Missing (".$missing.") of ".$expected_field_count." expected output field(s) of '$cmd'") } } } ## @name ::filer_fcp_adapters_get ## @summary Returns an array of hashes of fcp adapter fields and values for the all fcp adapters ## @arg $fc Required: The filer connection object where this function is executed sub filer_fcp_adapters_get ($) { my $fc = shift; my @arrayOfHashes = (); my $counter= -1; my ($key, $value); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate( $fc)) { my @api_args = ("fcp-adapter-list-info", "verbose","true"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # get adapters info my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; # logcomment("key: $key, value: $value"); if ($key eq 'adapter') { $counter++; if ($value =~ /.*_\d+$/) { ($arrayOfHashes[$counter]{'Slot'},undef) = split("_",$value); } else { # Slot is adapter $arrayOfHashes[$counter]{'Slot'} = $value; } } $arrayOfHashes[$counter]{$key} = $value; } } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "fcp show adapter -v"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); my $port = ''; foreach my $line (@info) { if ($line !~ /:/) {next} ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $key =~ s/\s+$//; $value =~ s/^\s+//; $value =~ s/\s+$//; if ($key eq "Slot") { $counter++; $port = ''; # No longer working on the ports for an adapter } elsif ($key eq "Adapter Name" ) { # Note each port has its own section if ($line =~ /.*_\d+$/) { (undef,$port) = split("_",$line); } } if ($counter >= 0) { if ($port eq '') { $arrayOfHashes[$counter]{$key} = $value; } else { $arrayOfHashes[$counter]{$key."_$port"} = $value; } } } } return @arrayOfHashes; } ## @name ::filer_fcp_adapters_slot_get ## @summary Returns an array of all fcp target adapter slots ## @arg $fc Required: The filer connection object where this function is executed sub filer_fcp_adapters_slot_get ($) { my $fc = shift; my @adapter_info = filer_fcp_adapters_get($fc); my @slots; foreach my $now_adapter (@adapter_info) { if (defined(${$now_adapter}{&fcp_adapter_field_slot})) { push(@slots,${$now_adapter}{&fcp_adapter_field_slot}); } else { ExitScriptError("No slot name defined by filer_fcp_adapters_get for an FCP target adapter"); } } print("Discovered these FCP target adapter slots: ".join(',',@slots)."\n"); return @slots; } ## @name ::filer_fcp_adapters_verbose_check ## @summary Verifies all information is displayed ## by the 'fcp show adapter' command for all fcp target adapters. ## @description ## Examines the 'fcp show adapter' output. ## Verifies each field is displayed for each adapter. ## @arg $fc Required: The filer connection object where this function is executed ## @arg @adapter Optional: All fcp target adapter names sub filer_fcp_adapters_verbose_check ($;@) { my $fc = shift; my @adapters = @_; my @adapter_info = filer_fcp_adapters_verbose_get($fc); my $specified_adapter_count = @adapters; my $observed_adapter_count = @adapter_info; if (@adapters) { #logcomment("Test expects $adaptercount FCP adapters on this filer."); } else { logcomment("Found $observed_adapter_count FCP adapters on this filer."); } if ($specified_adapter_count != $observed_adapter_count) { ExitSoftError("Expected $specified_adapter_count FCP target adapters, observed $observed_adapter_count"); } my $cmd = (); if ($main::FILER_TEST_UI =~ m/ZAPI/i) { $cmd = 'fcp-adapter-list-info'; } else { # Use ONTAP CLI $cmd = 'fcp show adapter -v'; } my $linesOfOutput=0; # Determine the type of output format to expect my @expected_fields; for my $now_adapter (@adapter_info) { no strict 'refs'; if ($main::FILER_TEST_UI =~ m/ZAPI/i) { if (${$now_adapter}{&fcp_adapter_zapi_field_adapter} =~ m/.*_\d+$/) { # A virtual port definition was seen @expected_fields = @filer_fcp_adapter_verbose_with_virtual_port_zapi_fields; print("ZAPI - FCP virtual ports detected\n"); } else { @expected_fields = @filer_fcp_adapter_verbose_zapi_fields; print("ZAPI - No FCP virtual ports detected\n"); } } else { # Use ZAPI CLI if (defined(${$now_adapter}{&fcp_adapter_name_0})) { @expected_fields = @filer_fcp_adapter_verbose_with_virtual_port_fields; print("FCP virtual ports detected\n"); } else { @expected_fields = @filer_fcp_adapter_verbose_fields; print("No FCP virtual ports detected\n"); } } for my $info ( keys %$now_adapter) { my $keyword = ''; for my $akeyword (@expected_fields) { if ($akeyword =~ /^$info$/i ) { $keyword = $akeyword; if ($info ne $keyword) { ExitSoftError("Expected keyword '$keyword', got '$info'"); } last; } } if ($keyword eq '') { ExitSoftError("Unknown keyword '$info' from '$cmd', did specifications change?"); } else { $linesOfOutput++; } } use strict 'refs'; } # Verify expected count of lines of output my $expected_field_count = @expected_fields*$observed_adapter_count; if ($linesOfOutput != $expected_field_count) { if ($expected_field_count<$linesOfOutput) { my $extra = $linesOfOutput-$expected_field_count; ExitSoftError("Duplicate field(s) (".$extra.") reported by 'fcp show adapter -v'") } else { my $missing = $expected_field_count-$linesOfOutput; ExitSoftError("Missing (".$missing.") out of ".$expected_field_count." expected output field(s) for ".$observed_adapter_count." adapters of 'fcp show adapter -v'") } } } ## @name ::filer_fcp_adapters_verbose_get ## @summary Returns an array of hashes of fcp adapter fields and values for the all fcp adapters ## @arg $fc Required: The filer connection object where this function is executed sub filer_fcp_adapters_verbose_get ($) { my $fc = shift; my @arrayOfHashes = (); my $counter=0; my ($key, $value); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-adapter-list-info", "verbose","true"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # get adapters info my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; # logcomment("key: $key, value: $value"); if ($key eq 'adapter') { if (@arrayOfHashes != 0) { $counter++; } # zapi does not provide slot, work around parsing adapter if ($value =~ m/.*_\d+$/) { ($arrayOfHashes[$counter]{'Slot'},undef) = split("_",$value); } else { # Slot is adapter $arrayOfHashes[$counter]{'Slot'} = $value; } } $arrayOfHashes[$counter]{$key} = $value; } } logcomment("adapter num: @arrayOfHashes, $counter"); } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating avfiler context $cmd = "fcp show adapter -v"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); my $port = ''; foreach my $line (@info) { if ($line !~ /:/) {next} my ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; if ((($key eq "Slot") || ($key eq "Adapter Resets")) && (@arrayOfHashes != 0)) { $counter++; $port = ''; # No longer working on the ports for an adapter } elsif ($key eq "Adapter Name" ) { # Note each port has its own section if ($line =~ /.*_\d+$/) { (undef,$port) = split("_",$line); } } if ($port eq '') { $arrayOfHashes[$counter]{$key} = $value; } else { $arrayOfHashes[$counter]{$key."_$port"} = $value; } } } return @arrayOfHashes; } ## @name ::filer_fcp_cfmode_get ## @summary Get the current FCP cluster failover mode of a filer ## @description The 'fcp show cfmode' command is issued on the specified filer connection. ## The value it displays is returned. ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_cfmode_get ($) { my $fc = shift; my $cfmode = ''; my $cmd = "fcp show cfmode"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); $cfmode = pop @info; chomp $cfmode; # take off any message prefix $cfmode =~ s/fcp show cfmode:\s+//; if ($cfmode !~ /dual_fabric|mixed|standby|partner|single_image|unknown/) { ExitSoftError("Unrecognized FCP cfmode '$cfmode' from ($cmd): $cfmode"); } } return $cfmode } ## @name ::filer_fcp_cfmode_set ## @summary Set the FCP cluster failover mode ## @description The FCP cfmode value is established. ## The FCP service is stopped and restarted if necessary. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $mode Required: The fcp cluster failover mode that needs to be set sub filer_fcp_cfmode_set ($$) { my $fc = shift; my $cfmode = arg_cfmode(shift); my $need_start = 0; if (filer_fcp_is_started($fc)) { filer_fcp_stop($fc); $need_start = 1; # Restart fcp if it was started initially } my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { $cmd = "ZAPI - fcp-set-cfmode, fcp-cfmode=$cfmode"; ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), "fcp-set-cfmode", "fcp-cfmode", $cfmode); } else { # Use ONTAP CLI # Must use -f option to prevent interactive prompts $cmd = "fcp set cfmode -f $cfmode"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); if ($cfmode ne filer_fcp_cfmode_get($fc)) { ExitError("FCP cfmode '$cfmode' could not be established'"); } } if ($need_start) { # Restart fcp if it was started initially filer_fcp_start($fc); } } ## @name ::filer_fcp_help ## @summary Verify help text for fcp subcommands ## @arg $fc Required: The filer connection object where this function is executed ## @arg $subcommand Required: The fcp subcommand to check help on ## @arg @expected a list of expected values each passed as a regular expression sub filer_fcp_help ($$;@) { my($fc) = shift; my($subcommand) = shift; my(@expected) = @_; my @rsh_blocked_commands = ('setup'); my @deprecated_commands = ('setup'); my $conn_type = $fc-> conntype(); my $orig_priv; if ($conn_type =~ m/telnet/) { $orig_priv = filer_cli_priv($fc); } # 'fcp show adapters' is displayed as 'fcp show adpater' my $displayed_subcommand = $subcommand; $displayed_subcommand =~ s/adapters/adapter/; $displayed_subcommand =~ s/initiators/initiator/; my $cmd = "fcp help $subcommand"; if ($subcommand =~ m/set\s+cfmode/) { # This command is only supported in advanced mode $cmd = "priv set advanced;$cmd"; } elsif ($subcommand =~ m/setup/) { # burt 129439 made this a diag command # then it removed it as a command altogether! $cmd = "priv set diag;$cmd"; } my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $numLinesMatch = 0; if ( 0 0) { my $not_seen = join("\n",@notseen); ExitSoftError("Missing expected help text output: '$not_seen'"); } ExitSoftError("Help information not displayed:", @info) if ($numLinesMatch < 2); } # Reset priv level if ($conn_type =~ m/telnet/ and $cmd =~ m/priv set/) { filer_cli_priv($fc,$orig_priv); } return $text; } ## @name ::filer_fcp_igroup_create ## @summary Creates an initiator group ## @arg $fc Required: The filer connection object where this function is executed ## @arg $igroup Required: Name of the initiator group ## @arg @portnames Optional: list of initiator WWPN port names sub filer_fcp_igroup_create ($$;@) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my(@portnames) = @_; my($portname) = join(' ',@portnames); my $return = 1; # Assume it works my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-create", "initiator-group-name", $igroup, "initiator-group-type", 'fcp'); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } if (!$result) { if (@portnames) { ($text, $result) = filer_igroup_node_add($fc,$igroup,@portnames); } } } else { # Use ONTAP CLI if (defined($portname)) { $cmd = "igroup create -f $igroup $portname"; } else { $cmd = "igroup create -f $igroup"; } ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup create\s?:/) { if (m/Initiator group already exists/) { if (filer_igroup_is_fcp($fc,$igroup)) { $return = 2; # it worked because it already existed last; } else { ExitError("The igroup $igroup already exists, but not for FCP"); } } else { ExitSoftError("Unexpected message from ($cmd): $line"); } } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn and 1 == $return) { ExitError("Unexpected return code ($result) from '$cmd'"); } } return $return; } ## @name ::filer_fcp_igroup_create_for_ostype ## @summary Creates an initiator group ## @arg $fc Required: The filer connection object where this function is executed ## @arg $igroup Required: Name of the initiator group ## @arg $ostype Required: OS type for the initiator group ## @arg @portname Optional: list of initiator WWPN port names sub filer_fcp_igroup_create_for_ostype ($$$;@) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($ostype) = arg_igroup_ostype(shift); my(@portnames) = @_; my($portname) = join(' ',@portnames); my $return = 1; # Assume it works my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-create", "initiator-group-name", $igroup, "initiator-group-type", 'fcp'); push (@api_args, "os-type", $ostype) if (defined($ostype)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } if (!$result) { if (@portnames) { filer_igroup_node_add($fc,$igroup,@portnames); } } } else { # Use ONTAP CLI if ($portname ne '') { $cmd = "igroup create -f -t $ostype $igroup $portname"; } else { $cmd = "igroup create -f -t $ostype $igroup"; } ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup create\s?:/) { if (m/Initiator group already exists/) { if (filer_igroup_is_fcp($fc,$igroup)) { $return = 2; # it worked because it already existed last; } else { ExitError("The igroup $igroup already exists, but not for FCP"); } } else { ExitSoftError("Unexpected message from ($cmd): $line"); } } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn and 1 == $return) { ExitError("Unexpected return code ($result) from '$cmd'"); } } return $return; } ## @name ::filer_fcp_igroup_create_for_ostype_portset ## @summary Creates an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $ostype Required:The ostype ('default','aix','hpux','linux,'solaris','windows') ## @arg $portset Required: Name of an existing portset to bind to. ## @arg @nodes Optional: List of initiator Node names sub filer_fcp_igroup_create_for_ostype_portset ($$$$;@) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($ostype) = arg_igroup_ostype(shift); my($portset) = shift; my(@nodes) = @_; my $node = join(' ',@nodes); my $return = 1; # Assume it works my $cmd = "igroup create -f -t $ostype -a $portset $igroup $node"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup create\s?:/) { if (m/Initiator group already exists/) { if (filer_igroup_is_iscsi($fc,$igroup)) { $return = 2; # it worked because it already existed last; } else { ExitError("The igroup $igroup already exists, but not for iSCSI"); } } elsif (m/Initiator group cannot bind to an empty portset,add ports to portset first/) { ExitError("The portset $portset is empty, cannot bind"); } elsif (m/No such portset exists/) { ExitError("The portset $portset does not exist"); } else { ExitSoftError("Unexpected message from ($cmd): $line"); } } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn and 1 == $return) { ExitError("Unexpected return code ($result) from '$cmd'"); } return $return; } ## @name ::filer_fcp_initialize ## @summary Licenses and starts FCP service on the filer if possible ## @ description Returns 1 if service is now started, otherwise 0 ## @arg $fc Required: The filer connection object for the command line interface ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub filer_fcp_initialize ($;$) { my($fc, $prefix) = @_; my($initialized)=0; if (filer_fcp_is_installed($fc, $prefix)) { filer_fcp_license($fc) if (!filer_fcp_is_licensed($fc)); if (!filer_fcp_is_started($fc)) { filer_fcp_start($fc); sleep(10); # Avoid seeing 'Link up' messages in future commands } $initialized=1; } return $initialized; } ## @name ::filer_fcp_initiator_check ## @summary Validates information displayed by 'fcp show initiator(s) $adapter' for ## the initiator(s) associated with the specified fcp target adapter name. ## @description ## Returns 1 if one of the specified initiators is connected. ## Returns 0 if no specified initiator is connected to that adapter ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Required: The fcp target adapter name ## @arg @initwwpns Required: The initiators' WWPN array sub filer_fcp_initiator_check($$@) { my $fc = shift; my $adapter = shift; my @in_initwwpns = @_; my @initwwpns; foreach my $initwwpn (@in_initwwpns) { if (defined($initwwpn)) { $initwwpn =~ s/://g; # drop colons for comparision push(@initwwpns,$initwwpn); } else { logresult('WARN',"An undefined CLIENT WWPN value was detected, verify CLIENT configurations"); } } if (@in_initwwpns == 0) { ExitConfigError("No WWPN initiator values were specified to verify"); } my $return = 0; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-adapter-initiators-list-info"); push (@api_args, "fcp-adapter", $adapter) if ($adapter ne ''); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # verify my ($key, $value); my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; #logcomment("key: $key, value: $value"); if($key eq 'port-name') { $value =~ s/://g; # drop colons for comparision; foreach my $initwwpn (@initwwpns) { if ($value =~ /^$initwwpn/) { $return = 1; } } } } } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "fcp show initiator $adapter"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { $line =~ s/://g; # drop colons for comparision foreach my $initwwpn (@initwwpns) { if ($line =~ /^$initwwpn/) { $return = 1; } } } } return $return; } ## @name ::filer_fcp_initiators_check ## @summary Validates information displayed ## by the 'fcp show initiator(s)' command. ## @arg $fc Required: The filer connection object where this function is executed sub filer_fcp_initiators_check ($) { my $fc = shift; my $cmd = "fcp show initiator"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); burt_begin(68465); my $cmd2 = "fcp show initiators"; my($text2,$result2) = filer_cli_say($fc,$cmd2); if ($result ne $result2) { ExitSoftError("Return codes vary between equivalent commands ($cmd): '$result' and ($cmd2): '$result2'"); } if ($text ne $text2) { ExitSoftError("Displayed text varies between equivalent commands ($cmd) and ($cmd2)"); } burt_end(68465); my(%adapters); my($port); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /Initiators connected on adapter/) { $port = $line; $port =~ s/.*Initiators connected on adapter (\w+):.*/$1/; print("port=$port\n"); #DEBUG } elsif ($line =~ /running on behalf of the partner/) { $port = $line; $port =~ s/.*Adapter (\w+)\s+.*/$1/; print("clustered port=$port\n"); #DEBUG $adapters{$port}{'status'} = "clustered"; } elsif ($line =~ /Portname/) { $adapters{$port}{'status'} = "connected"; } elsif ($line =~ /None connected/) { $adapters{$port}{'status'} = "notconnected"; } } return %adapters; } ## @name ::filer_fcp_is_installed ## @summary Determines if FCP is installed on the filer. ## @description ## Determines if FCP is installed on the filer by looking ## for the existence of a Fibre Channel Target Host Adapter. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub filer_fcp_is_installed ($;$) { my($fc, $prefix) = @_; my %params = Tharn::param_merge(hostrec => $fc->hostp(), param_prefix => $prefix); return $params{FCP_INSTALLED} =~ /^y/i; } ## @name ::filer_fcp_is_licensable ## @summary Determines if FCP can be licensed on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_is_licensable ($) { my($fc) = shift; return eval(filer_service_is_licensable ($fc,'fcp')); } ## @name ::filer_fcp_is_licensed ## @summary Determines if FCP is licensed on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_is_licensed ($) { my($fc) = shift; return eval(filer_service_is_licensed ($fc,'fcp')); } ## @name ::filer_fcp_is_started ## @summary Determines if FCP is started on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_is_started ($) { my($fc) = shift; return filer_service_is_started($fc,'fcp'); } ## @name ::filer_fcp_license ## @summary Licenses FCP on the filer ## @arg $fc Required: The filer connection object where this function is executed ## @arg $days Optional number of days license duration. Default is permanent license sub filer_fcp_license ($;$) { my($fc) = shift; my $days = shift; if (!defined($days) ) { $days = ''; } my $text = filer_service_license ($fc,'fcp','',$days); burt_begin(71307); if ($text =~ /virtual disks/) { ExitSoftError("The 'license fcp' output should not mention 'virtual disks'"); } burt_end(71307); } ## @name ::filer_fcp_nodename_get ## @summary Get the current FCP Nodename of a filer ## @description The 'fcp nodename' command is issued on the specified filer connection. ## The value it displays is returned. ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_nodename_get ($) { my($fc) = shift; my($nodename) = ''; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-node-get-name"); $cmd = "ZAPI - ".join(',',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), @api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } # get nodename $nodename = &ZapiUtil::zapi_parse_output($text, "node-name"); } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "fcp nodename "; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($text =~ /Too many arguments/) { burt_found(71214); } else { burt_fixed(71214); } if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); $nodename = pop @info; # Fibre Channel nodename: 50:a9:80:00:02:00:c4:c3 (50a980000200c4c3) if ($nodename !~ /Fibre Channel nodename: ..:..:..:..:..:..:..:.. (................)/) { ExitSoftError("Unrecognized output from ($cmd): $nodename"); } else { $nodename =~ s/.*: (..:..:..:..:..:..:..:..) .*/$1/; } } } return $nodename } ## @name ::filer_fcp_nodename_set ## @summary Set the FCP Nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: The fcp Nodename that needs to be set sub filer_fcp_nodename_set ($$) { my($fc) = shift; my($nodename) = shift; my $attemptsallowed = 2; while ($attemptsallowed > 0) { $attemptsallowed--; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("fcp-node-set-name", "node-name", $nodename); $cmd = "ZAPI - ".join(',',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), @api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "fcp nodename $nodename"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } if ($text =~ /Fibre Channel nodename set/) { $attemptsallowed = 0; # It worked, get out } elsif ($text =~ /Before modifying the nodename, you must stop the FCP service by entering the 'fcp stop' command\./) { if (defined($filer_fcp_is_started)) { if ($filer_fcp_is_started == 0) { burt_found(69818); ExitSoftError("FCP nodename could not be set after fcp service was stopped."); } else { my @msg = ("Script appears to attempt to set FCP nodename after fcp service was started.", "It should be stopped, or all fcp adapters taken offline."); ExitSoftError(@msg); } # Retry the command after a slight delay to see if it takes a while to 'offline' sleep(10); } } else { ExitError("Verification text 'Fibre Channel nodename set' not found for '$cmd'"); } } # Ontap CLI } } ## @name ::filer_fcp_start ## @summary Starts FCP on the filer ## @description Starts the fcp service ## and returns 0 if it was started ## or 1 if it was already started. ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_start ($) { my($fc) = shift; $filer_fcp_is_started = 1; if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), "fcp-service-start"); if ($result eq '0') { return 0; } elsif ($result eq '13013') { return 1; # Already started } elsif ($result eq '9112') { return 2; # single_image WWNN conflict } else { ExitError("Unexpected ZAPI errno: $result, text: '$text'"); } } else { # Use ONTAP CLI return filer_service_start ($fc,'fcp'); } } ## @name ::filer_fcp_stats_check ## @summary Checks fcp stats information ## @description ## It runs the filer_fcp_adapter_stats_get routine ## to get values back for the adapter specified. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $adapter Optional: a list of expected fcp target adapter names sub filer_fcp_stats_check ($;@) { my $fc = shift; my @adapters = @_; my %totalstats; my $num_fields=0; my $num_message_fields = 0; my $cmd = 'fcp stats'; #logcomment("Trace: filer_fcp_stats_check"); my $adaptercount; if (@adapters) { $adaptercount = @adapters; } else { # Determine adapter list and count from the filer @adapters = (); my @adapter_arrayofhashes = filer_fcp_adapters_get($fc); $adaptercount = @adapter_arrayofhashes; logcomment("Number of FCP adapters found on this filer: $adaptercount"); $adaptercount--; while ($adaptercount >0) { # Select ZAPI or CLI check if ($main::FILER_TEST_UI =~ m/ZAPI/i) { if (defined($adapter_arrayofhashes[$adaptercount]{'adapter'})) { if (defined($adapter_arrayofhashes[$adaptercount]{'partner-adapter'}) ) { if ($adapter_arrayofhashes[$adaptercount]{'partner-adapter'} eq 'None' ) { push(@adapters,$adapter_arrayofhashes[$adaptercount]{'adapter'}); } } elsif (defined($adapter_arrayofhashes[$adaptercount]{'adapter-type'})) { if ($adapter_arrayofhashes[$adaptercount]{'adapter-type'} eq 'Local' ) { push(@adapters,$adapter_arrayofhashes[$adaptercount]{'adapter'}); } } else { print("Ignoring $cmd output for adapter slot: $adapter_arrayofhashes[$adaptercount]{'adapter'}\n"); } } else { my $found_slots = ''; foreach my $key (keys %{$adapter_arrayofhashes[$adaptercount]}) { $found_slots .= ("$key: $adapter_arrayofhashes[$adaptercount]{$key} "); } ExitSoftError("No slot reported for FCP adapter: $adaptercount, but found these values: $found_slots\n"); } } else { # ONTAP CLI if (defined($adapter_arrayofhashes[$adaptercount]{'Slot'})) { if (defined($adapter_arrayofhashes[$adaptercount]{'Partner Adapter'}) ) { if ($adapter_arrayofhashes[$adaptercount]{'Partner Adapter'} eq 'None' ) { push(@adapters,$adapter_arrayofhashes[$adaptercount]{'Slot'}); } } elsif (defined($adapter_arrayofhashes[$adaptercount]{'Adapter Type'})) { if ($adapter_arrayofhashes[$adaptercount]{'Adapter Type'} eq 'Local' ) { push(@adapters,$adapter_arrayofhashes[$adaptercount]{'Slot'}); } } else { print("Ignoring $cmd output for adapter slot: $adapter_arrayofhashes[$adaptercount]{'Slot'}\n"); } } else { my $found_slots = ''; foreach my $key (keys %{$adapter_arrayofhashes[$adaptercount]}) { $found_slots .= ("$key: $adapter_arrayofhashes[$adaptercount]{$key} "); } ExitSoftError("No slot reported for FCP adapter: $adaptercount, but found these values: $found_slots\n"); } } $adaptercount--; } } $cmd = "fcp stats"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my %stats = (); my $counter=0; my $port_counter=0; my $field_counter=0; my @info = filer_cli_split($text); my $adapter_port = ''; foreach my $line (@info) { #Statistics for adapter if ($line =~ /Statistics for adapter\s+(\S+)/) { $adapter_port = $1; $port_counter++; next; } if ($line !~ /:/) {next} my ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; if ($key eq 'Message') { $adapter_port = 'Message'; next; } if ($adapter_port eq 'vtic') { if (grep(/^$key$/,@filer_fcp_stats_vtic_fields)) { } else { ExitSoftError("Unrecognized vtic field: '$key' from: $cmd"); next; } } elsif ($adapter_port eq 'Message') { if (grep(/^$key$/,@filer_fcp_stats_message_fields)) { } else { ExitSoftError("Unrecognized message field: '$key' from: $cmd, did specifications change?"); next; } } elsif ($adapter_port ne '') { if (grep(/^$key$/,@filer_fcp_stats_fields)) { } else { ExitSoftError("Unrecognized port $adapter_port field: '$key' from: $cmd, did specifications change?"); next; } } else { ExitSoftError("Unrecognized field: '$key' from: $cmd, did specifications change?"); } $field_counter++; if (defined($stats{$key})) { $stats{$key} += $value; } else { $stats{$key} = $value; } } # Now check each adapter's output # Get totals from these reports rather than the single one foreach my $adapter (@adapters) { my $linesOfOutput=0; my %adapter_info = filer_fcp_adapter_stats_get($fc,$adapter); no strict 'refs'; for my $info ( keys %adapter_info) { my $keyword = ''; for my $akeyword (@filer_fcp_stats_fields) { if ($akeyword =~ /$info/i ) { $keyword = $akeyword; if ($info ne $keyword) { ExitSoftError("Expected keyword '$keyword', got '$info'"); } last; } } if ($keyword eq '') { ExitSoftError("Unknown keyword '$info' from '$cmd $adapter', did specifications change?"); } else{ $totalstats{"$keyword"}+=$adapter_info{$info}; } $linesOfOutput++; use strict 'refs'; } # verify expected count of lines of output my $expected_field_count = @filer_fcp_stats_fields; # Check if adapter is 2Gb or not, if yes the expected field count will be 11 less my $command = "fcp show adapter -v $adapter"; my($text,$result) = filer_cli_say($fc,$command,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /Description:(.*)QLogic 2322(.*)/i) { $expected_field_count = $expected_field_count - 11; #2Gb adapter do not have SFP information } } if ($linesOfOutput != $expected_field_count) { if ($expected_field_count<$linesOfOutput) { ExitSoftError("Duplicate field(s) reported by '$cmd $adapter'") } else { my $missing = $expected_field_count-$linesOfOutput; ExitSoftError("Missing (".$missing.") of ".$expected_field_count." expected output field(s) of '$cmd $adapter'") } } } return %totalstats; } ## @name ::filer_fcp_stats_zero ## @summary Zeroes the FCP statistics for all FCP adapters. ## Verifies that no fcp stats data is displayed. ## @arg $fc Required: The filer connection object where this function is executed sub filer_fcp_stats_zero ($) { my $fc = shift; my $cmd = "fcp stats -z"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } # Verify no fcp stats data is displayed - it just zeroes the counts without display for my $akeyword (@filer_fcp_stats_fields) { if ($text =~ /$akeyword/ ) { ExitSoftError("Found unexpected keyword '$akeyword' in '$cmd' output"); } } } ## @name ::filer_fcp_status_get ## @summary Returns FCP status on the filer ## @description If the fcp service is running returns 1, ## or 0 if it is not running. ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_status_get ($) { my($fc) = shift; return filer_service_is_started ($fc,'fcp'); } ## @name ::filer_fcp_stop ## @summary Starts FCP on the filer ## @description Stops the fcp service ## and returns 0 if it was stopped ## or 1 if it was already stopped. ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_stop ($) { my($fc) = shift; # burt 73638 has requested fcp stats be run before stopping fcp filer_fcp_stats_check($fc); $filer_fcp_is_started = 0; my $status; burt_begin(70828); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(), "fcp-service-stop"); if ($result eq '0') { $status = 0; } elsif ($result eq '13014') { $status = 1; # Already stopped } else { ExitError("Unexpected ZAPI errno: $result, text: '$text'"); } } else { # Use ONTAP CLI $status = filer_service_stop ($fc,'fcp'); } burt_end(70828); return $status; # Remove this line if burt 69818 is no longer fixed # fcp target adapters may still be online see burt 69818 # To provide test script syncing operations, we need to wait here # for the adapters to go offline. foreach my $adapter ( @main::FILER_FCP_TARGET_NAMES ) { # If there are no partner target names, wait for all if (@FILER_FCP_TARGET_PARTNERS == 0) { while (filer_fcp_adapter_is_online($fc,$adapter)) { sleep(2); } } else { # If this is a partner, wait for it if (0'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /Can't open directory/) { last; } elsif ($line =~ /^sst_/ ) { # Just save the SCSI trace log file names push(@files,$line); } } return @files; } ## @name ::filer_fcp_trace_start ## @summary Starts FCP on the filer ## @description Starts the fcp trace ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_trace_start ($) { my($fc) = shift; my $cmd = "fcp trace on"; my($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($result eq cmdFailedReturn) { if ($text =~ /already on/) { ExitSoftError('-burt',85890,"Command worked but return code says it failed "); } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_fcp_trace_stop ## @summary Stops FCP on the filer ## @description Stops the fcp trace ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_trace_stop ($) { my($fc) = shift; my $cmd = "fcp trace off"; my($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($result eq cmdFailedReturn) { if ($text =~ /already off/) { ExitSoftError('-burt',85890,"Command worked but return code says it failed "); } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_fcp_unlicense ## @summary Unlicenses FCP on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_fcp_unlicense ($) { my($fc) = shift; filer_service_unlicense ($fc,'fcp'); } ## @name ::filer_igroup_alua_get ## @summary Gets ALUA param for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_alua_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $text; if (defined($igroup)) { $text = filer_igroup_show_verbose($fc,$igroup); } else { $text = filer_igroup_show_verbose($fc); } if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # verify my ($key, $value); my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; #logcomment("key: $key, value: $value"); if($key eq 'initiator-group-alua-enabled') { return $value; } } } return undef; } else { # Use ONTAP CLI my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/) { ExitSoftError("Unexpected message from filer_igroup_show(): $line"); } elsif ($line =~ /ALUA:\s(\w+)/) { # testme (iSCSI): # OS Type: solaris # ALUA: Yes return $1; } } return undef; } } ## @name ::filer_igroup_alua_set ## @summary Sets the ALUA param for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $alua Required: ALUA setting that the group set to ## @arg $force Optional: =1 to force, =0 to not force, default is 1 sub filer_igroup_alua_set ($$$;$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($alua) = shift; my($force) = shift; if (!defined($force)) { $force = 1; } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-set-attribute", "initiator-group-name", $igroup, "attribute", "alua", "value", $alua); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } else { # Use ONTAP CLI if ($force) { $cmd = "igroup set -f $igroup alua $alua"; } else { $cmd = "igroup set $igroup alua $alua"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_igroup_destroy ## @summary Destroys an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $force Optional: =1 to force, =0 to not force, default is 1 sub filer_igroup_destroy ($$;$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($force) = shift; if (!defined($force)) { $force = 1; } my $msg = ''; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-destroy", "initiator-group-name", $igroup, "force", $force); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } else { # Use ONTAP CLI if ($force) { $cmd = "igroup destroy -f $igroup"; } else { $cmd = "igroup destroy $igroup"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ m/igroup destroy:/) { if ($text =~ m/No such initiator group exists/) { # Script writer should call filer_igroup_vaporize to avoid this message $msg = "Initiator group cannot be deleted, it does not exist"; } elsif ($text =~ m/LUN maps for this initiator group exist/) { # Script writer should call filer_igroup_vaporize to avoid this message $msg = "Initiator group cannot be deleted since LUN maps exist"; } else { $msg = "Unexpected message from ($cmd)"; } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { $msg = "Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"; } } if ($msg ne '') { if (filer_igroup_is_defined($fc,$igroup)) { ExitError($msg); } else { logcomment($msg); } } } ## @name ::filer_igroup_help ## @summary Show help text for igroup subcommands ## @arg $fc Required: The filer connection object where this function is executed ## @arg $subcommand Required: The igroup subcommand to check help on ## @arg @expected a list of expected values each passed as a regular expression sub filer_igroup_help ($$;@) { my($fc) = shift; my($subcommand) = shift; my(@expected) = @_; my $command = 'igroup'; my $cmd = "$command help $subcommand"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/$command: help not found/) { if ($cmd =~ m/vfiler run/ ) { ExitSoftError('-burt',104271,"$command help was not recognized in a vfiler context"); } else { ExitSoftError("$command help was not recognized outside of a vfiler context"); } } else { my $numLinesMatch = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ( ($line =~ /$command $subcommand/) || ($line =~ / - \w+/) ){ $numLinesMatch++; } } my @notseen = (); foreach my $expected (@expected) { if (0 == grep { /$expected/ } @info) { push(@notseen,$expected); } } if (@notseen >0) { my $not_seen = join("\n",@notseen); ExitSoftError("Missing expected help text output: '$not_seen'"); } ExitSoftError("Help information not displayed:", @info) if ($numLinesMatch < 2); } } ## @name ::filer_igroup_is_defined ## @summary Determines if the specified igroup is defined on the filer ## @description ## Determines if an initiator group exists using the 'igroup show' command. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: The igroup name sub filer_igroup_is_defined ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-list-info"); push (@api_args, "initiator-group-name", $igroup) if (defined($igroup)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq cmdOkReturn) { return 1; } elsif ($result eq VDISK_ERROR_NO_SUCH_INITGROUP) { return 0; } else { ExitSoftError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); return 0; } } else { # Use ONTAP CLI $cmd = "igroup show $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok',timeout => 120}); # 60 failed my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ m/ostype:/) { return 1; } elsif ($line =~ m/No such initiator group exists/) { return 0; } } } ExitSoftError("Output did not clearly state that igroup was defined or not"); return 0; } ## @name ::filer_igroup_is_fcp ## @summary Returns 1 if the initiator group is for FCP use ## Returns 0 if not for FCP ## Returns undef if an error was reported or igroup does not exist ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_is_fcp ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-list-info"); push (@api_args, "initiator-group-name", $igroup) if (defined($igroup)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq cmdOkReturn) { if ($text =~ /fcp<\/initiator-group-type>/) { return 1; } elsif ($text =~ /iscsi<\/initiator-group-type>/) { return 0; } } elsif ($result eq VDISK_ERROR_NO_SUCH_INITGROUP) { return undef; } else { ExitSoftError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); return undef; } } else { # Use ONTAP CLI $cmd = "igroup show $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok',timeout => 120}); # 60 failed my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup show\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); # rtpqa-intel-3 (FCP) (ostype: windows): } elsif ($line =~ /\s\(FCP\)\s/) { return 1; } elsif ($line =~ /\s\(iSCSI\)\s/) { return 0; } } } ExitSoftError("Output did not clearly state that igroup was FCP or not"); return undef; } ## @name ::filer_igroup_is_iscsi ## @summary Returns 1 if the initiator group is for iSCSI use ## Returns 0 if not for iSCSI ## Returns undef if an error was reported or igroup does not exist ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_is_iscsi ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-list-info"); push (@api_args, "initiator-group-name", $igroup) if (defined($igroup)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq cmdOkReturn) { if ($text =~ /\s\(FCP\)\s/) { return 0; } elsif ($text =~ /\s\(iSCSI\)\s/) { return 1; } } elsif ($result eq VDISK_ERROR_NO_SUCH_INITGROUP) { return undef; } else { ExitSoftError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); return undef; } } else { # Use ONTAP CLI $cmd = "igroup show $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok',timeout => 120}); # 60 failed my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup show\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); # rtpqa-intel-3 (FCP) (ostype: windows): } elsif ($line =~ /\s\(FCP\)\s/) { return 0; } elsif ($line =~ /\s\(iSCSI\)\s/) { return 1; } } } ExitSoftError("Output did not clearly state that igroup was iSCSI or not"); return undef; } ## @name ::filer_igroup_lun_lunid_get ## @summary Returns the LUN ID of the specified LUN in the specified initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: The initiator group name ## @arg $path Required: The lun_path sub filer_igroup_lun_lunid_get ($$$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $path = arg_path(shift); # enforce lun_path conventions my $lunid = undef; my $cmd = "lun show -m -g $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $lun = ""; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /^LUN Path/i) { next; } elsif ($line =~ /^---/) { next; } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\/vol\//) { $lun = $line; $lun =~ s/^(\S+).*/$1/; if ($lun eq $path) { my $data = $line; $data =~ s/\S+\s+\S+\s+(\d+).*/$1/; $lunid = $data; last; } } else { ExitSoftError("Unexpected output from ($cmd): $line"); } } return $lunid; } ## @name ::filer_igroup_lun_show_get ## @summary Shows the luns associated with a specific initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: The initiator group name sub filer_igroup_lun_show_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my %HashOfHashes; my $cmd = "lun show -v -g $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $lun = ""; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /nospacereserve/i) { ExitSoftError('-burt',73589,"An old keyword 'nospacereserve' was found in ($cmd) output: $line"); } if (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /\/vol\//) { $lun = $line; $lun =~ s/\s+(\/\w+\/\w+\/\w+)\s+\w+ \(\d+\).*/$1/; $HashOfHashes{$lun}{'Lun Name'} = $lun; $HashOfHashes{$lun}{'Size'} = $line; $HashOfHashes{$lun}{'Size'} =~ s/\s+.*\s+(\w+) \(\d+\).*/$1/; } elsif ($line =~ /Serial/) { my $serial = $line; $serial =~ s/Serial#: "(.*)".*/$1/; $HashOfHashes{$lun}{'Serial'} = $serial; } elsif ($line =~ /Share/) { my $share = $line; $share =~ s/Share: "(.*)".*/$1/; $HashOfHashes{$lun}{'Share'} = $share; } elsif ($line =~ /Maps:/) { my @maps = split(/\s+/, $line); my @export = splice(@maps, 1); $HashOfHashes{$lun}{'Exports'} = "@export"; } else { # Exact format of success message is not defined, but must be validated } } return %HashOfHashes; } ## @name ::filer_igroup_lunid_lun_get ## @summary Returns the lun_path of the specified LUN ID in the specified initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: The initiator group name ## @arg $path Required: The lun_path sub filer_igroup_lunid_lun_get ($$$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $lunid = arg_lunid(shift); # enforce lunid conventions my $path = undef; my $cmd = "lun show -m -g $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /^LUN Path/i) { next; } elsif ($line =~ /^---/) { next; } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif (($line =~ /lun show: No such initiator group exists/)) { last; } elsif (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^(\/vol\/\S+\/\S+)\s+\S+\s+$lunid\s*\S*$/) { $path = $1; last; } elsif ($line =~ /^\/vol\//) { # Just another valid LUN entry } else { ExitSoftError("Unexpected output from ($cmd): $line"); } } return $path; } ## @name ::filer_igroup_luns_get ## @summary Returns the luns mapped to the initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: The initiator group name sub filer_igroup_luns_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my @luns = (); my $cmd = "lun show -m -g $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /^LUN Path/i) { next; } elsif ($line =~ /^---/) { next; } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif (($line =~ /lun show: No such initiator group exists/)) { last; } elsif (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ m/(\/vol\S+)\s+\S+\s+\d+/) { #lun_path igroup lunid push(@luns,$1); } elsif (($line =~ /lun show: No such initiator group exists/)) { last; } } return @luns; } ## @name ::filer_igroup_lunids_get ## @summary Returns the lun ids mapped to the initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: The initiator group name sub filer_igroup_lunids_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $cmd = "lun show -m -g $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @lunids = (); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /^LUN Path/i) { next; } elsif ($line =~ /^---/) { next; } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif (($line =~ /lun show: No such initiator group exists/)) { last; } elsif (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ m/\/vol\S+\s+\S+\s+(\d+)/) { #lun_path igroup lunid push(@lunids,$1); } } return @lunids; } ## @name ::filer_igroup_node_add ## @name ::filer_igroup_node_add ## @summary Adds a node to an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $node Node name of an initiator sub filer_igroup_node_add ($$@) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my(@portnames) = shift; my($portname) = join(' ',@portnames); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # igroup-add can not add more than one node # workaround to add one at a time my @nodenames = split(' ',$portname); foreach my $node (@nodenames) { my @api_args = ("igroup-add", "initiator-group-name", $igroup); push (@api_args, "initiator", $node); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "igroup add $igroup $portname"; ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ m/igroup add\s?:/) { if ($line =~ m/Duplicate node name ignored/) { logcomment("Duplicate node name attempt was properly ignored"); } else { ExitError("Unexpected message from ($cmd): $line"); } } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { my @nodes = filer_igroup_nodenames_get($fc,$igroup); foreach my $node (@portnames) { # Issue FATAL error is any requested node was not added if (0 == grep(/^$node$/,@nodes)) { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } ExitSoftError("All igroup nodes were added, but unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } } ## @name ::filer_igroup_node_remove ## @summary Removes a node from an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $node Required: Node name of an initiator in the group ## or an array reference to a list of node names ## @arg $force Optional: =1 to force, 0=0 to not force, default is 1 sub filer_igroup_node_remove ($$$;$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($node) = shift; my($force) = shift; if (!defined($force)) { $force = 1; } my @nodes; if (ref($node) eq 'ARRAY') { @nodes = @$node; } else { @nodes = ($node); } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { foreach my $a_node (@nodes) { my @api_args = ("igroup-remove", "initiator-group-name", $igroup, "initiator", $a_node, "force", $force); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } } else { # Use ONTAP CLI my $cmd_prefix = "igroup remove "; if ($force) { $cmd_prefix = $cmd_prefix."-f "; } $cmd_prefix = $cmd_prefix."$igroup"; foreach my $a_node (@nodes) { $cmd = "$cmd_prefix $a_node"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup remove\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { my @nodes = filer_igroup_nodenames_get($fc,$igroup); if (0 < grep(/^$node$/,@nodes)) { ExitError("igroup node was not removed, Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } else { ExitSoftError("igroup node was removed, but unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } } } } ## @name ::filer_igroup_nodenames_get ## @summary Returns an array of nodenames for the specified initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_nodenames_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my @nodenames = (); my %igroup_info = filer_igroup_show_get($fc,$igroup); return split(' ',$igroup_info{$igroup}{'Nodename'}); } ## @name ::filer_igroup_os_get ## @summary Gets the OS Type for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_os_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $cmd = "igroup show $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok',timeout => 120}); # 60 failed my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /ostype:\s(\w+)\)/) { # rtpqa-intel-3 (FCP) (ostype: windows): return $1; } } ExitScriptError("No 'ostype' information was found"); } ## @name ::filer_igroup_os_set ## @summary Sets the OS Type for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $ostype Required: OS Type that the group get's changed to ## @arg $force Optional: =1 to force, =0 to not force, default is 1 sub filer_igroup_os_set ($$$;$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($ostype) = shift; my($force) = shift; if (!defined($force)) { $force = 1; } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-set-attribute", "initiator-group-name", $igroup, "attribute", "ostype", "value", $ostype); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } else { # Use ONTAP CLI if ($force) { $cmd = "igroup set -f $igroup ostype $ostype"; } else { $cmd = "igroup set $igroup ostype $ostype"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_igroup_rename ## @summary Rename an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $new_igroup Required: New name of the initiator group sub filer_igroup_rename ($$$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($new_igroup) = arg_igroup(shift); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-rename", "initiator-group-name", $igroup); push (@api_args, "initiator-group-new-name", $new_igroup); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "igroup rename $igroup $new_igroup"; ($text,$result) = filer_cli_say($fc,$cmd); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup rename\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_igroup_show ## @summary Returns text string of output from the 'igroup show' command for the specified initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Name of the initiator group, default shows all sub filer_igroup_show ($;$) { my($fc) = shift; my($igroup) = shift; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-list-info"); push (@api_args, "initiator-group-name", $igroup) if (defined($igroup)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # Convert the output to the CLI format? } else { # Use ONTAP CLI if (defined($igroup)) { $igroup = arg_igroup($igroup); # enforce valid igroup name arguments $cmd = "igroup show $igroup"; } else { $cmd = "igroup show "; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok',timeout => 120}); # 60 failed my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup show:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } } } return $text; } ## @name ::filer_igroup_show_verbose ## @summary Returns text string of output from the 'igroup show' command for the specified initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Name of the initiator group, default shows all sub filer_igroup_show_verbose ($;$) { my($fc) = shift; my($igroup) = shift; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-list-info"); push (@api_args, "initiator-group-name", $igroup) if (defined($igroup)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # Convert the output to the CLI format? } else { # Use ONTAP CLI if (defined($igroup)) { $igroup = arg_igroup($igroup); # enforce valid igroup name arguments $cmd = "igroup show -v $igroup"; } else { $cmd = "igroup show -v"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok',timeout => 120}); # 60 failed my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup show:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } } } return $text; } ## @name ::filer_igroup_show_get ## @summary Gets a hash of all the info give in igroup show ## Hash keys correspond to the ZAPI tags ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Name of the initiator group, default shows all sub filer_igroup_show_get ($;$$) { my($fc) = shift; my $igroup = shift; my $text; if (!defined($igroup)) { $text = filer_igroup_show_verbose($fc); } else { $igroup = arg_igroup($igroup); # enforce valid igroup name arguments $text = filer_igroup_show_verbose($fc,$igroup); } my %HashOfHashes; my $group = ""; if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # $text will be in ZAPI output format $text =~ s/^//; while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag1 = $1; $text =~ s/^<(.*?)>//; if ($tag1 eq 'initiator-groups') { while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag2 = $1; $text =~ s/^<(.*?)>//; if ($tag2 eq "\/$tag1") { last; } elsif ($tag2 eq 'initiator-group-info') { while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag3 = $1; $text =~ s/^<(.*?)>//; if ($tag3 eq "\/$tag2") { last; } elsif ($tag3 eq 'initiator-group-name') { if ($text =~ m/(.*?)<\/$tag3>/) { $group = $1; $HashOfHashes{$group}{'Group Name'} = $group; $HashOfHashes{$group}{$tag3} = $group; #print("$group.$tag3=$group\n"); $text =~ s/.*?<\/$tag3>//; } else { ExitSoftError("Unexpected $tag3 value from ZAPI igroup show: $text"); } } elsif ($tag3 eq 'initiator-group-type') { if ($text =~ m/(.*?)<\/$tag3>/) { $HashOfHashes{$group}{'Protocol'} = $1; $HashOfHashes{$group}{$tag3} = $1; #print("$group.$tag3=$1\n"); $text =~ s/.*?<\/$tag3>//; } else { ExitSoftError("Unexpected $tag3 value from ZAPI igroup show: $text"); } } elsif ($tag3 eq 'initiator-group-os-type') { if ($text =~ m/(.*?)<\/$tag3>/) { $HashOfHashes{$group}{'OS Type'} = $1; $HashOfHashes{$group}{$tag3} = $1; #print("$group.$tag3=$1\n"); $text =~ s/.*?<\/$tag3>//; } else { ExitSoftError("Unexpected $tag3 value from ZAPI igroup show: $text"); } } elsif ($tag3 =~ m/^(initiator-group-throttle-borrow|initiator-group-throttle-reserve)$/) { if ($text =~ m/(.*?)<\/$tag3>/) { $HashOfHashes{$group}{$tag3} = $1; #print("$group.$tag3=$1\n"); $text =~ s/.*?<\/$tag3>//; } else { ExitSoftError("Unexpected $tag3 value from ZAPI igroup show: $text"); } } elsif ($tag3 =~ m/^(initiator-group-use-partner|initiator-group-vsa-enabled|initiator-group-alua-enabled)$/) { # These appear in the ZAPI docs as initiator-info tags # Yet, by their name, seem to be part of the initiator-group-info if ($text =~ m/(.*?)<\/$tag3>/) { $HashOfHashes{$group}{$tag3} = $1; #print("$group.$tag3=$1\n"); $text =~ s/.*?<\/$tag3>//; } else { ExitSoftError("Unexpected $tag3 value from ZAPI igroup show: $text"); } } elsif ($tag3 eq 'initiators') { $HashOfHashes{$group}{'Nodename'} = ''; while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag4 = $1; $text =~ s/^<(.*?)>//; if ($tag4 eq "\/$tag3") { last; } elsif ($tag4 eq 'initiator-info') { while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag5 = $1; $text =~ s/^<(.*?)>//; if ($tag5 eq "\/$tag4") { last; } elsif ($tag5 eq 'initiator-name') { if ($text =~ m/(.*?)<\/$tag5>/) { my $new_node = $1; $text =~ s/^.*?<\/$tag5>//; #print("$group.$tag3=$new_node\n"); if ($HashOfHashes{$group}{'Nodename'} eq '') { $HashOfHashes{$group}{'Nodename'} = $new_node; } else { # Build a blank separated string of nodes $HashOfHashes{$group}{'Nodename'} = $HashOfHashes{$group}{'Nodename'}.' '.$new_node; } } else { ExitSoftError("Unexpected $tag5 value from ZAPI igroup show: $text"); } } else { ExitSoftError("Unexpected $tag4 tag: $tag5 from ZAPI igroup show: $text"); } } else { ExitSoftError("Unexpected $tag4 content from ZAPI igroup show: $text"); } } } else { ExitSoftError("Unexpected $tag3 tag: $tag4 from ZAPI igroup show: $text"); } } else { ExitSoftError("Unexpected $tag3 content from ZAPI igroup show: $text"); } } } else { ExitSoftError("Unexpected $tag2 tag: $tag3 from ZAPI igroup show: $text"); } } else { ExitSoftError("Unexpected $tag2 content from ZAPI igroup show: $text"); } } } else { ExitSoftError("Unexpected $tag1 tag: $tag2 from ZAPI igroup show: $text"); } } else { ExitSoftError("Unexpected $tag1 content from ZAPI igroup show: $text"); } } } elsif($tag1 eq '/results') { last; } else { ExitSoftError("Unexpected level 1 tag: $tag1 from ZAPI igroup show: $text"); } } else { ExitSoftError("Unexpected content from ZAPI igroup show: $text"); } } } else { # Use ONTAP CLI # $text will be in CLI output format my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup show\s?:/) { # These messages should have been reported in filer_igroup_show #ExitSoftError("Unexpected message from (igroup show $igroup): $line"); } elsif ($line =~ /ostype:/) { $group = $line; # igroup_of_none_but_a_very_long_initiator_group_name (iSCSI) (ostype: default): $group =~ s/\s+(\w+)\s+\(\w+\)\s+\(\w+: \w+\):/$1/; $HashOfHashes{$group}{'initiator-group-name'} = $group; $HashOfHashes{$group}{'Group Name'} = $group; $HashOfHashes{$group}{'initiator-group-type'} = $line; $HashOfHashes{$group}{'initiator-group-type'} =~ s/\s+\w+ \((\w+)\) \(\w+: \w+\):/$1/; $HashOfHashes{$group}{'Protocol'} = $HashOfHashes{$group}{'initiator-group-type'}; $HashOfHashes{$group}{'initiator-group-os-type'} = $line; $HashOfHashes{$group}{'initiator-group-os-type'} =~ s/\s+\w+ \(\w+\) \(\w+: (\w+)\):/$1/; $HashOfHashes{$group}{'OS Type'} = $HashOfHashes{$group}{'initiator-group-os-type'}; $HashOfHashes{$group}{'Nodename'} = ''; } elsif ($line =~ /\s+(\S+)\s+\((FCP|iSCSI)\).*/) { $group = $1; $HashOfHashes{$group}{'initiator-group-name'} = $group; $HashOfHashes{$group}{'Group Name'} = $group; $HashOfHashes{$group}{'initiator-group-type'} = $line; $HashOfHashes{$group}{'initiator-group-type'} =~ s/\s+\w+ \((\w+)\):/$1/; $HashOfHashes{$group}{'Protocol'} = $HashOfHashes{$group}{'initiator-group-type'}; $HashOfHashes{$group}{'Nodename'} = ''; } elsif ($line =~ /OS Type:/) { $HashOfHashes{$group}{'initiator-group-os-type'} = $line; $HashOfHashes{$group}{'initiator-group-os-type'} =~ s/\s+\w+ \w+: (\w+)/$1/; $HashOfHashes{$group}{'OS Type'} = $HashOfHashes{$group}{'initiator-group-os-type'}; } elsif ($line =~ /(..:..:..:..:..:..:..:..)/) { # An FCP nodename has been found my $nodename = $1; # $nodename =~ s/^\s*(..:..:..:..:..:..:..:..).*/$1/; # $nodename =~ s/(..:..:..:..:..:..:..:..).*/$1/; $HashOfHashes{$group}{'Nodename'} = $HashOfHashes{$group}{'Nodename'} . " " . "$nodename"; $HashOfHashes{$group}{'Nodename'} =~ s/^ //; #} elsif ($line =~ /^\s+(iqn\.\S+)/ or $line =~ /^\s+(eui\.\S+)/) { } elsif ($line =~ /(iqn\.\S+)/ or $line =~ /(eui\.\S+)/) { # An iSCSI nodename has been found my $nodename = $1; $HashOfHashes{$group}{'Nodename'} = $HashOfHashes{$group}{'Nodename'} . " " . "$nodename"; $HashOfHashes{$group}{'Nodename'} =~ s/^ //; } elsif ($line =~ /ALUA:/) { $HashOfHashes{$group}{'initiator-group-alua-enabled'} = $line; $HashOfHashes{$group}{'initiator-group-alua-enabled'} =~ s/\s+\w+: (\w+)/$1/; $HashOfHashes{$group}{'ALUA'} = $HashOfHashes{$group}{'initiator-group-alua-enabled'}; } else { # Exact format of success message is not defined, but must be validated } } } return %HashOfHashes; } ## @name ::filer_igroup_throttle_borrow_get ## @summary Gets throttle borrow param for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_throttle_borrow_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $text; if (defined($igroup)) { $text = filer_igroup_show_verbose($fc,$igroup); } else { $text = filer_igroup_show_verbose($fc); } if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # verify my ($key, $value); my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; #logcomment("key: $key, value: $value"); if($key eq 'initiator-group-throttle-borrow') { return $value; } } } return undef; } else { # Use ONTAP CLI my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/) { ExitSoftError("Unexpected message from filer_igroup_show(): $line"); } elsif ($line =~ /Throttle Borrow:\s+(\w+)/) { # testme (iSCSI): # OS Type: solaris # Host Multipathing Software: Required # Throttle Reserve: 20% # Throttle Borrow: No return $1; } } return undef; } } ## @name ::filer_igroup_throttle_borrow_set ## @summary Sets the throttle_borrow param for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $reserve Required: throttle_reserve setting that the group set to ## @arg $force Optional: =1 to force, =0 to not force, default is 1 sub filer_igroup_throttle_borrow_set ($$$;$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($borrow) = shift; my($force) = shift; if (!defined($force)) { $force = 1; } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-set-attribute", "initiator-group-name", $igroup, "attribute", "throttle_borrow", "value", $borrow); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } else { # Use ONTAP CLI if ($force) { $cmd = "igroup set -f $igroup throttle_borrow $borrow"; } else { $cmd = "igroup set $igroup throttle_borrow $borrow"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { # warning message is ok if ($line =~ /igroup set\s?:/ and $line !~ /Throttle will be destroyed after active I\/O completes/) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_igroup_throttle_reserve_get ## @summary Gets throttle param for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_throttle_reserve_get ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my $text; if (defined($igroup)) { $text = filer_igroup_show_verbose($fc,$igroup); } else { $text = filer_igroup_show_verbose($fc); } if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # verify my ($key, $value); my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; #logcomment("key: $key, value: $value"); if($key eq 'initiator-group-throttle-reserve') { return $value; } } } return undef; } else { # Use ONTAP CLI my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/) { ExitSoftError("Unexpected message from filer_igroup_show(): $line"); } elsif ($line =~ /Throttle Reserve:\s+(\d+)\%/) { # testme (iSCSI): # OS Type: solaris # Host Multipathing Software: Required # Throttle Reserve: 20% # Throttle Borrow: No return $1; } } return undef; } } ## @name ::filer_igroup_throttle_reserve_set ## @summary Sets the throttle_reserve param for an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $reserve Required: throttle_reserve setting that the group set to ## @arg $force Optional: =1 to force, =0 to not force, default is 1 sub filer_igroup_throttle_reserve_set ($$$;$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($reserve) = shift; my($force) = shift; if (!defined($force)) { $force = 1; } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-set-attribute", "initiator-group-name", $igroup, "attribute", "throttle_reserve", "value", $reserve); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } } else { # Use ONTAP CLI if ($force) { $cmd = "igroup set -f $igroup throttle_reserve $reserve"; } else { $cmd = "igroup set $igroup throttle_reserve $reserve"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup set\s?:/ and $line !~ /Throttle will be destroyed after active I\/O completes/ ) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_igroup_vaporize ## @summary Forces destruction of an initiator group if it exists. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_vaporize ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments if (filer_igroup_is_defined($fc,$igroup) ) { # Remove all mappings of the initiator group # since destroy request will fail if any are mapped. my @luns = filer_igroup_luns_get($fc,$igroup); if (@luns >0 ) { logcomment("Vaporizing lun mappings in igroup: $igroup"); foreach my $lun (@luns) { filer_lun_unmap($fc,$lun,$igroup); } } # Now destroy the empty initiator group filer_igroup_destroy($fc,$igroup); } } ## @name ::filer_igroup_verify_destroy_is_disabled ## @summary Verifies that a specified initiator group cannot be destroyed ## Returns 1 if the igroup could not be destroyed, else 0 if it was destroyed ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group sub filer_igroup_verify_destroy_is_disabled ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($cmd,$text,$result,$fail_status); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-destroy", "initiator-group-name", $igroup); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq VDISK_ERROR_INITGROUP_MAPS_EXIST) { $fail_status = 0; } else { ExitSoftError("Expected return code: ".VDISK_ERROR_INITGROUP_MAPS_EXIST." got: $result from ZAPI: ".join(' ',@api_args)); $fail_status = 1; } } else { # Use ONTAP CLI $cmd = "igroup destroy $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result,$fail_status) = filer_cli_error_check($fc,$cmd,cmdFailedReturn,qr(igroup destroy:),qr(LUN maps for this initiator group exist)); } return !$fail_status; } ## @name ::filer_igroup_bind ## @summary Binds an initiator group to a portset. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $portset Required: Name of the portset. sub filer_igroup_bind ($$$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($portset) = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-bind-portset", "initiator-group-name", $igroup); push (@api_args, "portset-name", $portset); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd' "); } } else { # Use ONTAP CLI $cmd = "igroup bind $igroup $portset"; ($text,$result) = filer_cli_say($fc,$cmd); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup bind\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_igroup_unbind ## @summary Unbinds an initiator group from a portset. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $portset Required: Name of the portset. sub filer_igroup_unbind ($$) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("igroup-unbind-portset", "initiator-group-name", $igroup); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd' "); } } else { # Use ONTAP CLI $cmd = "igroup unbind $igroup"; ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup unbind\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not specified } } } } ## @name ::filer_iscsi_alias_clear ## @summary clear iscsi target alias ## @arg $fc Required: The filer connection object where this function is executed sub filer_iscsi_alias_clear ($) { my ($fc) = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-target-alias-clear-alias"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi alias -c"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } ## @name ::filer_iscsi_alias_get ## @summary get iscsi target alias ## Return alias name. undef if no alias assigned ## @arg $fc Required: The filer connection object where this function is executed sub filer_iscsi_alias_get ($) { my ($fc) = shift; my $alias_name; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-target-alias-get-alias"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # get alias name $alias_name = &ZapiUtil::zapi_parse_output($text, "is-alias-assigned", "alias-name"); } else { # Use ONTAP CLI $cmd = "iscsi alias"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } # get alias name $alias_name = $text; if($alias_name =~ m/^iSCSI target alias:\s+/) { $alias_name =~ s/iSCSI target alias:\s+//; } elsif ($alias_name =~ m/No iSCSI target alias/) { $alias_name = undef; } else { ExitSoftError("Unexpected output line: $text\n"); } } return $alias_name; } ## @name ::filer_iscsi_alias_set ## @summary clear iscsi target alias ## @arg $fc Required: The filer connection object where this function is executed ## @arg $aliasname Required: target alias name to be set sub filer_iscsi_alias_set ($$) { my ($fc) = shift; my ($aliasname) = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-target-alias-set-alias"); push (@api_args, "alias-name", $aliasname) if (defined($aliasname)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi alias $aliasname"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } ## @name ::filer_iscsi_help ## @summary Show help text for iscsi subcommands ## @arg $fc Required: The filer connection object where this function is executed ## @arg $subcommand Required: The iscsi subcommand to check help on ## @arg @expected a list of expected values each passed as a regular expression sub filer_iscsi_help ($$;@) { my($fc) = shift; my($subcommand) = shift; my(@expected) = @_; my $command = 'iscsi'; my $cmd = "$command help $subcommand"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/: unrecognized command/) { if ($subcommand =~ m/show\s+initiators/) { ExitSoftError('-burt',146130,"'initiators' should be accepted with the iscsi help show command"); } elsif ($cmd =~ m/vfiler run/ ) { ExitSoftError('-burt',103847,"$command help $subcommand was not recognized in a vfiler context"); } else { ExitSoftError("$command help $subcommand was not recognized outside of a vfiler context"); } } else { my $numLinesMatch = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ( ($line =~ /$command $subcommand/) || ($line =~ / - \w+/) ){ $numLinesMatch++; } } my @notseen = (); foreach my $expected (@expected) { if (0 == grep { /$expected/ } @info) { push(@notseen,$expected); } } if (@notseen >0) { my $not_seen = join("\n",@notseen); ExitSoftError("Missing expected help text output: '$not_seen'"); } else { # Verify at least one prototype statement was seen if ($numLinesMatch == 0) { ExitSoftError("No prototype statement was included in help text from: $cmd"); } } } } ## @name ::filer_iscsi_igroup_create ## @summary Creates an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg @nodes Optional: List of initiator Node names sub filer_iscsi_igroup_create ($$;@) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my(@nodes) = @_; my $node = join(' ',@nodes); my $return = 1; # Assume it works my($cmd, $text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ( "igroup-create", "initiator-group-name", $igroup, "initiator-group-type", 'iscsi'); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq cmdOkReturn) { if (@nodes) { filer_igroup_node_add($fc,$igroup,@nodes); } } } else { # Use ONTAP CLI $cmd = "igroup create -i $igroup $node"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup create\s?:/) { if (m/Initiator group already exists/) { if (filer_igroup_is_iscsi($fc,$igroup)) { $return = 2; # it worked because it already existed last; } else { ExitError("The igroup $igroup already exists, but not for iSCSI"); } } else { ExitSoftError("Unexpected message from ($cmd): $line"); } } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn and 1 == $return) { ExitError("Unexpected return code ($result) from '$cmd'"); } } if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from '$cmd'",$text); } return $return; } ## @name ::filer_iscsi_igroup_create_for_ostype ## @summary Creates an initiator group ## @arg $fc Required: The filer connection object for the command line interface ## @arg $igroup Required: Name of the initiator group ## @arg $ostype Required:The ostype ('default','aix','hpux','linux,'solaris','windows') ## @arg @nodes Optional: List of initiator Node names sub filer_iscsi_igroup_create_for_ostype ($$$;@) { my($fc) = shift; my($igroup) = arg_igroup(shift); # enforce valid igroup name arguments my($ostype) = arg_igroup_ostype(shift); my(@nodes) = @_; my $node = join(' ',@nodes); my $return = 1; # Assume it works my($cmd, $text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ( "igroup-create", "initiator-group-name", $igroup, "initiator-group-type", 'iscsi'); push (@api_args, "os-type", $ostype) if (defined($ostype)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from ZAPI: ".join(' ',@api_args)); } if (!$result) { if (@nodes) { filer_igroup_node_add($fc,$igroup,@nodes); } } } else { # Use ONTAP CLI $cmd = "igroup create -i -t $ostype $igroup $node"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /igroup create\s?:/) { if (m/Initiator group already exists/) { if (filer_igroup_is_iscsi($fc,$igroup)) { $return = 2; # it worked because it already existed last; } else { ExitError("The igroup $igroup already exists, but not for iSCSI"); } } else { ExitSoftError("Unexpected message from ($cmd): $line"); } } else { # Exact format of success message is not defined } } } if ($result ne cmdOkReturn and 1 == $return) { ExitError("Unexpected return code ($result) from '$cmd'"); } return $return; } ## @name ::filer_iscsi_initialize ## @summary Licenses and starts iSCSI service on the filer if possible ## @ description Returns 1 if service is now started, otherwise 0 ## @arg $fc Required: The filer connection object for the command line interface ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub filer_iscsi_initialize ($;$) { my($fc, $prefix) = @_; my($initialized)=0; if (filer_iscsi_is_installed($fc, $prefix)) { filer_iscsi_license($fc) if (!filer_iscsi_is_licensed($fc)); filer_iscsi_start($fc) if (!filer_iscsi_is_started($fc)); $initialized=1; } return $initialized; } ## @name ::filer_iscsi_initiator_check ## @summary Validates information displayed by 'iscsi initiator show' for ## the initiator(s). ## @description ## Returns the count of connected initiators. ## Flags iSCSI nodenames that are not extra or missing ## @arg $fc Required: The filer connection object where this function is executed ## @arg @initnodenames Option: A list of initiator iscsi nodenames sub filer_iscsi_initiator_check($@) { my $fc = shift; my @in_initnodenames = @_; # cull undefined and empty nodename elements out my @initnodenames = (); foreach (@in_initnodenames) { if (defined($_)) { if ($_ ne '') { push(@initnodenames,$_); } } } # Initialize flags for each expected report field my $got_initiators_connected_header = 0; my $got_target_portal_header = 0; my $got_message_no_initiators = 0; my $match_count = 0; my $initiator_count = 0; my %initiators; my %sessions; # for Tsingtao release, $adapter param is deprecated. my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-initiator-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # try to set the same flags info as CLI for compatibility if(defined(&ZapiUtil::zapi_parse_output($text,"iscsi-initiator-list-entries"))) { # use tag as initiator header $got_initiators_connected_header = 1; } # use tag as portal header if(defined(&ZapiUtil::zapi_parse_output($text,"iscsi-initiator-list-entry-info"))) { $got_target_portal_header = 1; %initiators = &ZapiUtil::zapi_parse_output($text,"target-session-id","tpgroup-tag","initiator-nodename","isid"); for my $init (keys %initiators) { if(defined($initiators{$init}{'initiator-nodename'})) { $initiator_count++; } if (grep(/^$initiators{$init}{'initiator-nodename'}$/i,@initnodenames)) { $match_count++; } else { my $initiator_list = join(',',@initnodenames); # Extra node names may be present if they are defined "outside" of the # test bed that was defined by testbed_setup logcomment("Initiator '$initiators{$init}{'initiator-nodename'}' not found in input initiator list: $initiator_list"); } } } else { $got_message_no_initiators = 1; } } else { # Use ONTAP CLI $cmd = "iscsi initiator show"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($text =~ m/iscsi initiator show:/) { ExitSoftError("Unexpected message from ($cmd)"); } else { if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } # parsing for initiator info my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ m/(?i:i)nitiators connected/) { $got_initiators_connected_header = 1; } elsif ( ($line =~ m/^\s+Tgt_PG\s+iSCSI Initiator Name \/ ISID/) || ($line =~ m/^\s+TSIH\s+TPGroup\s+Initiator/) ) { # for Tsingtao $got_target_portal_header = 1; } # For Tsingtao, it displays only one line; therefore code below need to be seperated. if ( ($line =~ m/None connected\./) || ($line =~ m/No initiators connected/) ) { $got_message_no_initiators = 1; last; } # Verify expected format # 2 iqn.1987-05.com.cisco:rtpqa-del2650-6 / 00:02:3d:00:00:01 if ($line =~ m/iqn\.|eui\./) { if ($line =~ m/\s\d+\s+(\S+)\s+\/\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ || $line =~ m/\s\d+\s+\S+\s+\((\S+)\s+\/\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ) { # 2 iqn.1987-05.com.cisco:rtpqa-del2650-6 / 00:02:3d:00:00:01 my $initiator = $1; my $wwnn = $2; if (defined($initiators{"$initiator"})) { if (defined($sessions{"$initiator $wwnn"})) { ExitSoftError('-burt',108696,"Initiator: $initiator ISID: $wwnn is listed more than once in report from ($cmd)"); } } else { $initiator_count++; $initiators{"$initiator"} = 1; if (grep(/^$initiator$/i,@initnodenames)) { $match_count++; } else { my $initiator_list = join(',',@initnodenames); # Extra node names may be present if they are defined "outside" of the # test bed that was defined by testbed_setup logcomment("Initiator '$initiator' not found in input initiator list: $initiator_list"); } } $sessions{"$initiator $wwnn"} = 1; } else { ExitSoftError("Unexpected initiator line in report from ($cmd): $line"); } } } } # Issue messages for output format failures if (!$got_initiators_connected_header) { ExitSoftError("Failed to see 'Initiators connected' or 'No initiator connected' heading in report from ($cmd)"); } elsif ($got_message_no_initiators) { # no further output verification is needed } elsif (!$got_target_portal_header) { ExitSoftError("Failed to see 'TSIH' heading in report from ($cmd)"); } elsif ($initiator_count eq '0') { ExitSoftError("Failed to see any initiators listed under the 'TSIH' heading in report from ($cmd)"); } if ($match_count ne @initnodenames) { logcomment("Expected iSCSI initiators: ". join(' ',@initnodenames)); ExitSoftError("Expected ".scalar(@initnodenames)." initiators, got $match_count in report from ($cmd)"); } return $initiator_count; } ## @name ::filer_iscsi_interface_status ## @summary Gets enabled/disabled status on each ethernet interface ## Return a hash of network interface keys and its status ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_interface_status ($) { my $fc = shift; my %Hash; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-interface-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string %Hash = &ZapiUtil::zapi_parse_output($text,"interface-name","is-interface-enabled"); } else { # Use ONTAP CLI $cmd = "iscsi interface show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { # Handle up to four port IP cards if ($line =~ /Interface (e\d+[abcdefgh]?) (enabled)/ || $line =~ /Interface (e\d+[abcdefgh]?) (disabled)/) { my $nic = $1; my $status = $2; if (defined($Hash{$nic})) { ExitSoftError("Duplicate status for $nic\n"); } else { $Hash{$nic} = $status; } } else { ExitSoftError("Unexpected output line: $line\n"); } } } return %Hash; } ## @name ::filer_iscsi_interface_status_check ## @summary Checks iscsi service status on an ethernet interface ## Return 1 if match expected status; 0 if not match expected status. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nic Required: The network interface name ## @arg $expected Required: The expected status sub filer_iscsi_interface_status_check ($$$) { my $fc = shift; my $nic = shift; my $expected = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-interface-list-info"); push (@api_args, "interface-name", $nic) if (defined($nic)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # zapi returned true or false if($expected eq "enabled") { $expected = "true"; } else { $expected = "false"; } } else { # Use ONTAP CLI $cmd = "iscsi interface show $nic"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } my $return = 0; if ($text =~ /$expected/) { $return = 1; } return $return; } ## @name ::filer_iscsi_interface_enable ## @summary Enables iscsi service on an ethernet interface ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nic Required: The network interface name sub filer_iscsi_interface_enable ($$) { my $fc = shift; my $nic = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-interface-enable"); push (@api_args, "interface-name", $nic) if (defined($nic)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi interface enable $nic"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } ## @name ::filer_iscsi_interface_disable ## @summary Disables iscsi service on an ethernet interface ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nic Required: The network interface name sub filer_iscsi_interface_disable ($$) { my $fc = shift; my $nic = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-interface-disable"); push (@api_args, "interface-name", $nic) if (defined($nic)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi interface disable -f $nic"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } ## @name ::filer_iscsi_is_installed ## @summary Determines if iSCSI is installed on the filer ## @description ## Determines if iSCSI is installed on the filer by looking ## for the FILER_ISCSI_INSTALLED = yes. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $prefix Optional: prefix of global param to be merged into hostrec params sub filer_iscsi_is_installed ($;$) { my ($fc, $prefix) = @_; my %params = Tharn::param_merge(hostrec => $fc->hostp(), param_prefix => $prefix); return $params{ISCSI_INSTALLED} =~ /^y/i; } ## @name ::filer_iscsi_is_licensable ## @summary Determines if iSCSI can be licensed on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_is_licensable ($) { my($fc) = shift; return filer_service_is_licensable ($fc,'iscsi'); } ## @name ::filer_iscsi_is_licensed ## @summary Determines if iSCSI is licensed on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_is_licensed ($) { my($fc) = shift; return filer_service_is_licensed ($fc,'iscsi'); } ## @name ::filer_iscsi_is_started ## @summary Determines if iSCSI is started on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_is_started ($) { my($fc) = shift; my ($service) = 'iscsi'; return filer_service_is_started($fc,'iscsi'); } ## @name ::filer_iscsi_license ## @summary Licenses iSCSI on the filer ## @arg $fc Required: The filer connection object for the command line interface ## @arg $days Optional number of days license duration. Default is permanent license sub filer_iscsi_license ($;$) { my($fc) = shift; my $days = shift; if (!defined($days) ) { $days = ''; } filer_service_license ($fc,'iscsi','',$days); } ## @name ::filer_iscsi_nodename_get ## @summary Get the current iSCSI Nodename of a filer ## @description The 'iscsi nodename' command is issued on the specified filer connection. ## The value it displays is returned. ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_nodename_get ($) { my($fc) = shift; my($nodename) = ''; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-node-get-name"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # get nodename $nodename = &ZapiUtil::zapi_parse_output($text, "node-name"); } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "iscsi nodename"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } # get alias name my @info = filer_cli_split($text); $nodename = pop @info; # iSCSI target nodename: iqn.1992-08.com.netapp.sn.12345678 if ($nodename !~ /iSCSI target nodename:\s+iqn\./) { ExitSoftError("Unrecognized output from ($cmd): $nodename"); } else { $nodename =~ s/\s*iSCSI target nodename:\s+(.*)\s*/$1/; } } return $nodename; } ## @name ::filer_iscsi_nodename_set ## @summary Set the iSCSI Nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: The iSCSI Nodename that needs to be set sub filer_iscsi_nodename_set ($$) { my($fc) = shift; my($nodename) = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-node-set-name"); push (@api_args, "node-name", $nodename) if (defined($nodename)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi nodename $nodename"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } # No confirmation text is returned } ## @name ::filer_iscsi_portal_get ## @summary Get the current iSCSI network portal of a filer ## @description The 'iscsi portal' command is issued on the specified filer connection. ## The value it displays is returned. ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_portal_get ($) { my($fc) = shift; my %Hash; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-portal-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string %Hash = &ZapiUtil::zapi_parse_output($text,"ip-address","ip-port","tpgroup-tag","interface-name"); } else { # Use ONTAP CLI $cmd = "iscsi portal show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { # Handle up to four port IP cards if ($line =~ /Network\s+portals:/) { # ignore this line next; # next line } elsif ($line =~ /IP\s+address/){ # ignore this line next; # next line } else { my ($ip, $tcpport, $tpgroup, $nic) = split ' ',$line, 4; #print("ip: $ip; tcpport: $tcpport, tpgroup: $tpgroup, nic: $nic\n"); if (defined($Hash{$ip})) { ExitSoftError("Duplicate hash key for $ip\n"); } else { $Hash{$ip}{'ip-port'} = $tcpport if (defined($tcpport)); $Hash{$ip}{'tpgroup-tag'} = $tpgroup if (defined($tpgroup)); $Hash{$ip}{'interface-name'} = $nic if (defined($nic)); } } } } return %Hash; } ## @name ::filer_iscsi_security_default_get ## @summary Get the iSCSI default security policy information ## @arg $fc Required: The filer connection object for the command line interface ## returns: A hash with up to three elements: type, inname and outname sub filer_iscsi_security_default_get ($) { my ($fc) = @_; my %returnHash; my $value; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-initiator-get-default-auth"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string if($text =~ m/CHAP/) { if($text =~ /outbound-user-name/) { %returnHash = &ZapiUtil::zapi_parse_output($text,"auth-type","user-name","outbound-user-name"); } else { %returnHash = &ZapiUtil::zapi_parse_output($text,"auth-type","user-name"); } } else { $returnHash{'auth-type'} = &ZapiUtil::zapi_parse_output($text,"auth-type"); } } else { # Use ONTAP CLI $cmd = "iscsi security show"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif ($line =~ /Default sec/i) { $value = $line; $value =~ s/.*Default sec is\s+(\w*).*/$1/; $returnHash{'type'} = $value; if ($value eq 'CHAP') { if ($line =~ /Inbound username:/) { $value = $line; $value =~ s/.*Inbound username:\s+(\w*).*/$1/; $returnHash{'inname'} = $value; } if ($line =~ /Outbound username:/) { $value = $line; $value =~ s/.*Outbound username:\s+(\w*).*/$1/; $returnHash{'outname'} = $value; } } last; } } } return %returnHash; } ## @name ::filer_iscsi_security_default_set ## @summary Set the iSCSI default security policy ## @arg $fc Required: The filer connection object for the command line interface ## @arg $sec_type Required: CHAP, deny, or none ## @arg %chap_args Optional: if default is CHAP, may specify names and passwords ## Valid values: inpass, inname, outpass, and outname sub filer_iscsi_security_default_set ($$;%) { my ($fc, $sec_type, %chap_args) = @_; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-initiator-set-default-auth"); push (@api_args, "auth-type", $sec_type) if (defined($sec_type)); # security type (CHAP, deny, none) if($sec_type eq "CHAP") { push (@api_args, "user-name", $chap_args{'inname'}) if (defined($chap_args{'inname'})); push (@api_args, "password", $chap_args{'inpass'}) if (defined($chap_args{'inpass'})); push (@api_args, "outbound-user-name", $chap_args{'outname'}) if (defined($chap_args{'outname'})); push (@api_args, "outbound-password", $chap_args{'outpass'}) if (defined($chap_args{'outpass'})); } $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } else { # Use ONTAP CLI my $cmd = "iscsi security default -s $sec_type "; my $chap_options = ""; if ($sec_type eq "CHAP") { if (defined($chap_args{'inpass'})) { $chap_options .= "-p $chap_args{'inpass'} "; } if (defined($chap_args{'inname'})) { $chap_options .= "-n $chap_args{'inname'} "; } if (defined($chap_args{'outpass'})) { $chap_options .= "-o $chap_args{'outpass'} "; } if (defined($chap_args{'outname'})) { $chap_options .= "-m $chap_args{'outname'} "; } $cmd .= $chap_options; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } } ## @name ::filer_iscsi_security_generate_passwd ## @summary Generate random CHAP password secret code ## @arg $fc Required: The filer connection object for the command line interface ## returns: Secret password code sub filer_iscsi_security_generate_passwd ($) { my ($fc) = shift; my $passwd; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-auth-generate-chap-password"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string $passwd = &ZapiUtil::zapi_parse_output($text,"secret"); } else { # Use ONTAP CLI $cmd = "iscsi security generate"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } # get password code $passwd = $text; if($passwd =~ m/^Generated Random Secret:\s+/) { $passwd =~ s/Generated Random Secret:\s+//; } else { ExitSoftError("Unexpected output line: $text\n"); } } return $passwd; } ## @name ::filer_iscsi_security_initiator_auth_get ## @summary Get the list of iSCSI initiator security policy information ## @arg $fc Required: The filer connection object for the command line interface ## @arg $initiator Optional: nodename ## returns: A hash with up to three elements: initiator, type, inname and outname or ## HashOfHash sub filer_iscsi_security_initiator_auth_get ($;$) { my ($fc) = shift; my ($initiator) = @_; my %returnHash; my $value; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-initiator-auth-list-info"); push (@api_args, "initiator", $initiator) if (defined($initiator)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string if($text =~ m/CHAP/) { if($text =~ /outbound-user-name/) { %returnHash = &ZapiUtil::zapi_parse_output($text,"auth-type","user-name","outbound-user-name"); } else { %returnHash = &ZapiUtil::zapi_parse_output($text,"auth-type","user-name"); } } else { $returnHash{'auth-type'} = &ZapiUtil::zapi_parse_output($text,"auth-type"); } } else { # Use ONTAP CLI $cmd = "iscsi security show"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /^\s*$/ or $line =~ /^===== / or $line =~ /Default sec/i) { next; # vfiler header lines or default security } elsif ($line =~ /^init:/i) { $value = $line; $value =~ s/^init:\s+(\w*).*/$1/; if(defined($initiator) and $value ne $initiator) { next; # defined $initiator and not match with $value goto next line } $returnHash{'type'} = $value; if ($value eq 'CHAP') { if ($line =~ /Inbound username:/) { $value = $line; $value =~ s/.*Inbound username:\s+(\w*).*/$1/; $returnHash{'inname'} = $value; } if ($line =~ /Outbound username:/) { $value = $line; $value =~ s/.*Outbound username:\s+(\w*).*/$1/; $returnHash{'outname'} = $value; } } } } } return %returnHash; } ## @name ::filer_iscsi_security_initiator_add ## @summary Add the iSCSI initiator security policy ## @arg $fc Required: The filer connection object for the command line interface ## @arg $initiator Required: nodename ## @arg $sec_type Required: CHAP, deny, or none ## @arg %chap_args Optional: if default is CHAP, may specify names and passwords ## Valid values: inpass, inname, outpass, and outname sub filer_iscsi_security_initiator_add ($$$;%) { my ($fc, $initiator, $sec_type, %chap_args) = @_; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-initiator-add-auth"); push (@api_args, "initiator", $initiator) if (defined($initiator)); push (@api_args, "auth-type", $sec_type) if (defined($sec_type)); # security type (CHAP, deny, none) if($sec_type eq "CHAP") { push (@api_args, "user-name", $chap_args{'inname'}) if (defined($chap_args{'inname'})); push (@api_args, "password", $chap_args{'inpass'}) if (defined($chap_args{'inpass'})); push (@api_args, "outbound-user-name", $chap_args{'outname'}) if (defined($chap_args{'outname'})); push (@api_args, "outbound-password", $chap_args{'outpass'}) if (defined($chap_args{'outpass'})); } logcomment("api args: @api_args"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } else { # Use ONTAP CLI my $cmd = "iscsi security add -i $initiator -s $sec_type "; my $chap_options = ""; if ($sec_type eq "CHAP") { if (defined($chap_args{'inpass'})) { $chap_options .= "-p $chap_args{'inpass'} "; } if (defined($chap_args{'inname'})) { $chap_options .= "-n $chap_args{'inname'} "; } if (defined($chap_args{'outpass'})) { $chap_options .= "-o $chap_args{'outpass'} "; } if (defined($chap_args{'outname'})) { $chap_options .= "-m $chap_args{'outname'} "; } $cmd .= $chap_options; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } } ## @name ::filer_iscsi_security_initiator_delete ## @summary Delete the iSCSI initiator security policy ## @arg $fc Required: The filer connection object for the command line interface ## @arg $initiator Required: nodename sub filer_iscsi_security_initiator_delete ($$) { my ($fc, $initiator) = @_; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-initiator-delete-auth"); push (@api_args, "initiator", $initiator) if (defined($initiator)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } else { # Use ONTAP CLI my $cmd = "iscsi security delete -i $initiator "; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } } ## @name ::filer_iscsi_start ## @summary Starts iSCSI on the filer ## @description Starts the iscsi service and returns 0 if it was started ## or 1 if it was already started. ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_start ($) { my($fc) = shift; return filer_service_start ($fc,'iscsi'); } ## @name ::filer_iscsi_stats_check ## @summary Checks iscsi stats information ## @description ## It runs the filer_iscsi_stats_get routine to get values. ## @arg $fc Required: The filer connection object where this function is executed sub filer_iscsi_stats_check ($) { my $fc = shift; my $num_fields=0; #logcomment("Trace: filer_iscsi_stats_check"); my %stats_info = filer_iscsi_stats_get($fc); my %totalstats = (); no strict 'refs'; for my $info ( keys %stats_info) { my $keyword = ''; for my $akeyword (@filer_iscsi_stats_fields) { if ($akeyword eq $info ) { $keyword = $akeyword; last; } } if ($keyword eq '') { ExitSoftError("Unknown keyword '$info' from 'iscsi stats', did specifications change?"); } else { $num_fields++; $totalstats{$keyword}+=$stats_info{$info}; } use strict 'refs'; } # verify expected count of keywords my $expected_field_count = @filer_iscsi_stats_fields; # For Tsingtao, $adaptercount will be 0. Need to remove below line # $expected_field_count = $expected_field_count*$adaptercount; if ($num_fields != $expected_field_count) { if ($expected_field_count<$num_fields) { ExitSoftError("Duplicate field(s) reported by 'iscsi stats'") } else { my $missingcount = $expected_field_count-$num_fields; ExitSoftError("Missing (".$missingcount.") of ".$expected_field_count." expected output field(s) of 'iscsi stats'") } } return %totalstats; } ## @name ::filer_iscsi_stats_field_get ## @summary Returns a specific iSCSI statistics value. ## @description ## Returns the iSCSI stats value or undefined. ## An error message is reported for undefined fields. ## ## @arg $fc Required: The filer connection object where this function is executed ## @arg $field Required: The text to identify the requested field. sub filer_iscsi_stats_field_get ($$) { my $fc = shift; my $field = shift; my %stats = filer_iscsi_stats_get($fc); if (exists($stats{$field}) ) { return $stats{$field}; } else { ExitSoftError("No value found for iscsi stats '$field'."); return undef; } } ## @name ::filer_iscsi_stats_get ## @summary Returns the iSCSI statistics. ## @description ## Returns a hash of iSCSI stats values. ## ## @arg $fc Required: The filer connection object where this function is executed ## default sums stats by keyword across all iscsi interfaces. sub filer_iscsi_stats_get ($) { my $fc = shift; logcomment("Trace: filer_iscsi_stats_get"); my $cmd = "iscsi stats"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my %stats = (); my $counter=0; my $pdus_received_block=0; my $pdus_transmitted_block=0; my $cdbs_block=0; my $errors_block=0; my @info = filer_cli_split($text); foreach my $line (@info) { ## Statistics for adapter xxx if ($line =~ /Statistics for adapter/) { # Starting a new adapter, reset flags $pdus_received_block=0; $pdus_transmitted_block=0; $cdbs_block=0; $errors_block=0; next; } elsif ($line =~ /iSCSI PDUs Received/) { ## iSCSI PDUs Received $pdus_received_block=1; next; } elsif ($line =~ /iSCSI ERRORS/) { ## iSCSI ERRORS $errors_block=1; next; } elsif ($line =~ /iSCSI PDUs Transmitted/) { ## iSCSI PDUs Transmitted $pdus_transmitted_block=1; next; } elsif ($line =~ /iSCSI CDBs/) { ## iSCSI CDBs $cdbs_block=1; next; } if ($line !~ /:/) {next} while ($line =~ /\|/) { my ($column, $newline) = split(/\|/, $line, 2); $line = $newline; if ($column =~ /:/) { my ($key, $value) = split(/:/, $column, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; $stats{$key} = $value; } } if ($line =~ /:/) { my ($key, $value) = split(/:/, $line, 2); $key =~ s/^\s+//; $value =~ s/^\s+//; if ($key eq 'Total') { my $total_value = $1; if ($errors_block == 1) { $stats{+iscsi_stats_field_total_errors} = $value; } elsif ($pdus_transmitted_block == 1) { $stats{+iscsi_stats_field_total_transmitted_pdus} = $value; } elsif ($pdus_received_block == 1) { $stats{+iscsi_stats_field_total_received_pdus} = $value; } } else { $stats{$key} = $value; } } } return %stats; } ## @name ::filer_iscsi_stats_zero ## @summary Zeroes the iSCSI statistics for all iSCSI adapters ## @arg $fc Required: The filer connection object where this function is executed sub filer_iscsi_stats_zero ($) { my $fc = shift; my $cmd = "iscsi stats -z"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_iscsi_status_check ## @summary Checks iscsi target status with the expected status. ## Return 1 if it matches or 0 if not. ## @arg $fc Required: The filer connection object ## @arg $exptected Required: The expected iscsi status sub filer_iscsi_status_check ($$) { my($fc) = shift; my $expected = shift; my $cmd = "iscsi status"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my ($text, $result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } my @info = filer_cli_split($text); my $return = 0; foreach my $line (@info) { if ($line =~ /$expected/) { $return = 1; last; } } return $return; } ## @name ::filer_iscsi_status_get ## @summary Returns iscsi service status on the filer ## @description If the iscsi service is running returns 1, ## or 0 if it is not running. ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_status_get ($) { my($fc) = shift; return filer_service_is_started ($fc,'iscsi'); } ## @name ::filer_iscsi_stop ## @summary Stops iSCSI on the filer ## @description Stops the iscsi service and returns 0 if it was stopped ## or 1 if it was already stopped. ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_stop ($) { my($fc) = shift; return filer_service_stop ($fc,'iscsi'); } ## @name ::filer_iscsi_tpgroup_check ## @summary check for tpgtag in tpgroup name ## @description checking for valid tpgtag in tpgroup name ## @arg $tpgroup Required: target portal group name ## @arg $tptag Required: target portal tag sub filer_iscsi_tpgroup_check ($$) { my($tpgroup) = arg_tpgroup(shift); # enforce valid tpgroup name arguments my($tpgtag) = arg_tpgtag(shift); # enforce valid tpgroup tag arguments my ($result) = 0; # check for valid tpgtag in tpgroup if ($tpgroup =~ m/$tpgtag/) { $result = 1; } return $result; } ## @name ::filer_iscsi_tpgroup_create ## @summary create iSCSI tpgroup on the filer ## @description Create the iscsi tpgroup ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgroup Required: target portal group name ## @arg $tptag Optional: target portal tag sub filer_iscsi_tpgroup_create ($$;$) { my($fc) = shift; my($tpgroup) = arg_tpgroup(shift); # enforce valid tpgroup name arguments my($tpgtag) = arg_tpgtag(shift); # enforce valid tpgroup tag arguments my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-create"); push (@api_args, "tpgroup-name", $tpgroup) if (defined($tpgroup)); push (@api_args, "tpgroup-tag", $tpgtag) if (defined($tpgtag)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi tpgroup create -f "; $cmd .= "-t $tpgtag " if (defined($tpgtag)); $cmd .= "$tpgroup " if (defined($tpgroup)); ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } return; } ## @name ::filer_iscsi_tpgroup_destroy ## @summary destroy iSCSI tpgroup on the filer ## @description destroy the iscsi tpgroup ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgroup Required: target portal group name sub filer_iscsi_tpgroup_destroy ($$) { my($fc) = shift; my($tpgroup) = arg_tpgroup(shift); # enforce valid tpgroup name arguments # zapi call may require tpggroup-tag instead of tpgroup-name my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-destroy"); # for zapi, tpgroup-destroy only accepts tpgroup-tag. see burt 158141 my $tpgrouptag = filer_iscsi_tpgroup_tag_get($fc, $tpgroup); push (@api_args, "tpgroup-tag", $tpgrouptag) if (defined($tpgrouptag)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi tpgroup destroy -f "; $cmd .= "$tpgroup " if (defined($tpgroup)); ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } return; } ## @name ::filer_iscsi_tpgroup_exist ## @summary checking the existing of iSCSI tpgroup on the filer ## @description checking existing of iscsi tpgroup name ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgroup Required: target portal group name to match sub filer_iscsi_tpgroup_exist ($$) { my($fc) = shift; my($tpgroup) = arg_tpgroup(shift); # enforce valid tpgroup name arguments my %Hash; my $retval = 0; %Hash = filer_iscsi_tpgroup_get($fc); for my $key (keys %Hash) { if($Hash{$key}{'tpgroup-name'} =~ m/$tpgroup/) { # match tpgroup name $retval = 1; } } return $retval; } ## @name ::filer_iscsi_tpgroup_get ## @summary get iSCSI tpgroup info on the filer ## @description getting info from iscsi tpgroup ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_tpgroup_get ($) { my($fc) = shift; my %Hash; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string %Hash = &ZapiUtil::zapi_parse_output($text,"tpgroup-tag","tpgroup-name","interface-name"); } else { # Use ONTAP CLI $cmd = "iscsi tpgroup show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { # Handle up to four port IP cards if ($line =~ /^TPGTag\s+/) { # ignore this line next; # next line } else { my ($tpgtag, $tpgroup, $nic) = split ' ',$line, 3; #print("tpgtag: $tpgtag, tpgroup: $tpgroup, nic: $nic\n"); if (defined($Hash{$tpgtag})) { ExitSoftError("Duplicate hash key for $tpgtag\n"); } else { $Hash{$tpgtag}{'tpgroup-name'} = $tpgroup if (defined($tpgroup)); $Hash{$tpgtag}{'interface-name'} = $nic if (defined($nic)); } } } } return %Hash; } ## @name ::filer_iscsi_tpgroup_interface_add ## @summary add network interface to existing iSCSI tpgroup on the filer ## @description add network interface name to the existing iscsi tpgroup ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgroup Required: target portal group name ## @arg $nic Required: network interface name sub filer_iscsi_tpgroup_interface_add ($$$) { my($fc) = shift; my($tpgroup) = arg_tpgroup(shift); # enforce valid tpgroup name arguments my($nic) = shift; # zapi call may require tpggroup-tag instead of tpgroup-name my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-interface-add"); # for zapi, tpgroup-interface-add only accepts tpgroup-tag. see burt 158141 my $tpgrouptag = filer_iscsi_tpgroup_tag_get($fc, $tpgroup); push (@api_args, "tpgroup-tag", $tpgrouptag) if (defined($tpgrouptag)); if(!defined($nic)) { ExitError("Invalid nic parameter") } else { push (@api_args, "interface-name", $nic); } $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi tpgroup add -f "; $cmd .= "$tpgroup " if (defined($tpgroup)); if(!defined($nic)) { ExitError("Invalid nic parameter") } else { $cmd .= "$nic " } ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } return; } ## @name ::filer_iscsi_tpgroup_interface_remove ## @summary remove network interface to existing iSCSI tpgroup name on the filer ## @description remove network interface name to the existing iscsi tpgroup ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgroup Required: target portal group name ## @arg $nic Required: network interface name sub filer_iscsi_tpgroup_interface_remove ($$$) { my($fc) = shift; my($tpgroup) = arg_tpgroup(shift); # enforce valid tpgroup name arguments my($nic) = shift; # zapi call may require tpggroup-tag instead of tpgroup-name my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-interface-delete"); # for zapi, tpgroup-interface-remove only accepts tpgroup-tag. see burt 158141 my $tpgrouptag = filer_iscsi_tpgroup_tag_get($fc, $tpgroup); push (@api_args, "tpgroup-tag", $tpgrouptag) if (defined($tpgrouptag)); if(!defined($nic)) { ExitError("Invalid nic parameter") } else { push (@api_args, "interface-name", $nic); } $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "iscsi tpgroup remove -f "; $cmd .= "$tpgroup " if (defined($tpgroup)); if(!defined($nic)) { ExitError("Invalid nic parameter") } else { $cmd .= "$nic " } ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } return; } ## @name ::filer_iscsi_tpgroup_name_get ## @summary get iSCSI tpgroup name w/ tpgroup tag parameter via ZAPI/CLI UI ## @description getting iscsi tpgroup name via ZAPI/CLI interface ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgtag Required: target portal group tag sub filer_iscsi_tpgroup_name_get ($$) { my($fc) = shift; my($tpgrouptag) = @_; my ($tpgroupname, %Hash); if ($tpgrouptag !~ /^\d+$/) { ExitScriptError("TPGroup TAG must be number instead of '$tpgrouptag'"); } my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-list-info"); push (@api_args, "tpgroup-tag", $tpgrouptag) if (defined($tpgrouptag)); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string %Hash = &ZapiUtil::zapi_parse_output($text,"tpgroup-tag","tpgroup-name","interface-name"); # get tpgroupname for my $tpgtag_key (keys %Hash) { if($tpgtag_key == $tpgrouptag) { $tpgroupname = $Hash{$tpgtag_key}{'tpgroup-name'}; last; } } $tpgroupname = $Hash{$tpgrouptag}{'tpgroup-name'}; } else { # Use ONTAP CLI $cmd = "iscsi tpgroup show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { # Handle up to four port IP cards if ($line =~ /^TPGTag\s+/) { # ignore this line next; # next line } else { my ($tpgtag, $tpgroup, $nic) = split ' ',$line, 3; #print("tpgtag: $tpgtag, tpgroup: $tpgroup, nic: $nic\n"); if (defined($Hash{$tpgtag})) { ExitSoftError("Duplicate hash key for $tpgtag\n"); } elsif ($tpgtag == $tpgrouptag) { $tpgroupname = $tpgroup; last; } } } } return $tpgroupname; } ## @name ::filer_iscsi_tpgroup_nic_available ## @summary get list of member network interface via ZAPI/CLI UI ## @description get a list of member network interface via ZAPI/CLI interface ## Return an array of member network interface on target portal group ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_tpgroup_nic_available ($) { my($fc) = shift; my (@array_nic, %Hash); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string %Hash = &ZapiUtil::zapi_parse_output($text,"tpgroup-tag","tpgroup-name","interface-name"); # get list of member nic for my $tpgtag_key (keys %Hash) { if($Hash{$tpgtag_key}{'interface-name'} !~ m/none/) { push (@array_nic, $Hash{$tpgtag_key}{'interface-name'}); } } } else { # Use ONTAP CLI $cmd = "iscsi tpgroup show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { # Handle up to four port IP cards if ($line =~ /^TPGTag\s+/) { # ignore this line next; # next line } else { my ($tpgtag, $tpgroup, $nic) = split ' ',$line, 3; #print("tpgtag: $tpgtag, tpgroup: $tpgroup, nic: $nic\n"); if (defined($Hash{$tpgtag})) { ExitSoftError("Duplicate hash key for $tpgtag\n"); } elsif ($nic !~ m/none/) { push (@array_nic, $nic); } } } } return @array_nic; } ## @name ::filer_iscsi_tpgroup_tag_get ## @summary get iSCSI tpgroup tag w/ tpgroup name parameter via ZAPI/CLI UI ## @description getting iscsi tpgroup tag via ZAPI/CLi interface ## @arg $fc Required: The filer connection object for the command line interface ## @arg $tpgroup Required: target portal group name sub filer_iscsi_tpgroup_tag_get ($$) { my($fc) = shift; my($tpgroupname) = arg_tpgroup(shift); # enforce valid tpgroup name arguments my ($tpgrouptag, %Hash); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-tpgroup-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check zapi result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # parse data from zapi output string %Hash = &ZapiUtil::zapi_parse_output($text,"tpgroup-tag","tpgroup-name","interface-name"); # get tpgroupname for my $tpgtag_key (keys %Hash) { if($Hash{$tpgtag_key}{'tpgroup-name'} =~ m/$tpgroupname/) { $tpgrouptag = $tpgtag_key; last; } } } else { # Use ONTAP CLI $cmd = "iscsi tpgroup show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } my @info = filer_cli_split($text); foreach my $line (@info) { # Handle up to four port IP cards if ($line =~ /^TPGTag\s+/) { # ignore this line next; # next line } else { my ($tpgtag, $tpgroup, $nic) = split ' ',$line, 3; #print("tpgtag: $tpgtag, tpgroup: $tpgroup, nic: $nic\n"); if (defined($Hash{$tpgtag})) { ExitSoftError("Duplicate hash key for $tpgtag\n"); } elsif ($tpgroup == $tpgroupname) { $tpgrouptag = $tpgtag; last; } } } } return $tpgrouptag; } ## @name ::filer_iscsi_unlicense ## @summary Unlicenses iSCSI on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_unlicense ($) { my($fc) = shift; filer_service_unlicense ($fc,'iscsi'); } ## @name ::filer_iscsi_session_check ## @summary Check result of 'iscsi session show' command ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_session_check ($) { my $fc = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-session-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } else { # Use ONTAP CLI $cmd = "iscsi session show"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } # check text output for both CLI and ZAPI return filer_iscsi_session_show_output_parse($text); } ## @name ::filer_iscsi_session_show_output_parse ## @summary Parse output of 'iscsi session show [-v]'. ## @description Return 0 if there is only 'No active sessions'. ## 1 if there is only session info. ## 2 if there ## are both, which is wrong. ## @arg $output Required: output of 'iscsi session show [-v]' sub filer_iscsi_session_show_output_parse ($) { my $output =shift; my $active = 0; my $no_active = 0; if ($main::FILER_TEST_UI =~ m/ZAPI/i) { my $init_name = &ZapiUtil::zapi_parse_output($output,"initiator-nodename"); if(!defined($init_name)) { return 0; } else { return 1; } } else { # ONTAP CLI my @info = filer_cli_split($output); foreach my $line (@info) { # Sample observed non-verbose output (NOT from the man page) # Session Initiator # Session 0 # Initiator Information # Initiator Name: iqn.1987-05.com.cisco:rtpqa-del2650-6 # ISID: 00:02:3d:00:00:01 if ($line =~ /No active session/) { $no_active = 1; } elsif ($line =~ /^\s+Initiator Name:/ ) { $active = 1; } } if ($no_active == 1 && $active == 0) { return 0; } elsif ($no_active == 0 && $active == 1) { return 1; } else { ExitSoftError("An iscsi session show command indicated both active and not active information."); return 2; } } } ## @name ::filer_iscsi_session_verbose_check ## @summary Check result of 'iscsi session show -v' command ## @arg $fc Required: The filer connection object for the command line interface sub filer_iscsi_session_verbose_check ($) { my $fc = shift; my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("iscsi-session-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } } else { # Use ONTAP CLI $cmd = "iscsi session show -v"; ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from command '$cmd'"); } } # check text output for both CLI and ZAPI return filer_iscsi_session_show_output_parse($text); } ## @name ::filer_isns_config ## @summary Configure the iSCSI iSNS definition ## @arg $fc Required: The filer connection object for the command line interface ## @arg $ip Required: The IP address of of the iSNS server sub filer_isns_config ($$) { my($fc) = shift; my($ip) = shift; my $cmd = "iscsi isns config -i $ip"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_isns_get ## @summary Get the iSCSI iSNS definition ## @arg $fc Required: The filer connection object for the command line interface ## Returns: Hash with the following keys: entity, serverip, and status sub filer_isns_get ($) { my($fc) = shift; my %data; my $cmd = "iscsi isns show"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); my $value; foreach my $line (@info) { if ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif ($line =~ /iSNS Entity id:/i) { $value = $line; $value =~ s/^.*iSNS Entity id:\s*//; $data{'entity'} = $value; } elsif ($line =~ /iSNS Server ip-addr/i) { $value = $line; $value =~ s/^.*iSNS Server ip-addr:\s*//; $data{'serverip'} = $value; } elsif ($line =~ /iSNS Status/i) { $value = $line; $value =~ s/^.*iSNS Status:\s*//; $data{'status'} = $value; } } if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } return %data; } ## @name ::filer_isns_start ## @summary Start the iSCSI iSNS use ## @arg $fc Required: The filer connection object for the command line interface sub filer_isns_start ($) { my($fc) = shift; my $cmd = "iscsi isns start"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_isns_stop ## @summary Stop the iSCSI iSNS use ## @arg $fc Required: The filer connection object for the command line interface sub filer_isns_stop ($) { my($fc) = shift; my $cmd = "iscsi isns stop"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_isns_update ## @summary Update the iSCSI iSNS definition ## @arg $fc Required: The filer connection object for the command line interface sub filer_isns_update ($) { my($fc) = shift; my $cmd = "iscsi isns update"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result) from '$cmd'"); } } ## @name ::filer_lun_attribute_get ## @summary Gets the attributes associated with a lun. ## @description This command requires a debug kernel. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_attribute_get ($$;$) { my($fc) = shift; my($path) = shift; my($attribute) = shift; $path = arg_path($path); # enforce lun_path conventions my %Hash; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { foreach my $attribute (@lun_attribute_names) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-get-attribute", "path", $path, "name", $attribute); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(.*?)<\/value>/) { $Hash{$attribute} = $1; } } } else { # Use ONTAP CLI my($cmd,$text,$result); $cmd = "lun attribute list $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_test_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if (($line =~ /lun attribute:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\s+enabled\s+=\s/) { my $temp = $line; $temp =~ s/^\s+enabled =\s+(.*)$/$1/; $Hash{'enabled'} = $temp; } elsif ($line =~ /^\s+path_last\s+=\s/) { my $temp = $line; $temp =~ s/^\s+path_last =\s+(.*)$/$1/; $Hash{'path_last'} = $temp; } elsif ($line =~ /^\s+\@Anvil\@FSConsistent\s+=\s/) { my $temp = $line; $temp =~ s/^\s+\@Anvil\@FSConsistent\s+=\s+(.*)$/$1/; $Hash{'AnvilFSConsistent'} = $temp; } elsif ($line =~ /^\s+host_stamp\s+=\s/) { my $temp = $line; $temp =~ s/^\s+host_stamp\s+=\s+(.*)$/$1/; $Hash{'host_stamp'} = $temp; } elsif ($line =~ /^\s+serial\s+=\s/) { my $temp = $line; $temp =~ s/^\s+serial\s+=\s+(.*)$/$1/; $Hash{'serial'} = $temp; } elsif ($line =~ /^\s+type\s+=\s/) { my $temp = $line; $temp =~ s/^\s+type\s+=\s+(.*)$/$1/; $Hash{'type'} = $temp; } elsif ($line =~ /^\s+spacereserve\s+=\s/) { my $temp = $line; $temp =~ s/^\s+spacereserve\s+=\s+(.*)$/$1/; $Hash{'spacereserve'} = $temp; } elsif ($line =~ /^\s+cylinder_size\s+=\s/) { my $temp = $line; $temp =~ s/^\s+cylinder_size\s+=\s+(.*)$/$1/; $Hash{'cylinder_size'} = $temp; } } } return %Hash } ## @name ::filer_lun_clone ## @summary Clone the specified lun. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lunpath Required: The lun_path for this LUN sub filer_lun_clone ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_clone_start($fc,$path); filer_lun_clone_wait($fc,$path) } ## @name ::filer_lun_clone_blockscomplete_get ## @summary Returns the cloning status of the lun in terms of blocks complete. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lunpath Required: The lun_path for this LUN sub filer_lun_clone_blockscomplete_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $blocks_complete = -1; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-clone-status-list-info", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(\d+)<\/blocks-completed>/) { $blocks_complete = $1; } else { ExitSoftError("No blocks-complete value returned"); } } else { # Use ONTAP CLI my $cmdname = "lun show -c"; # Was 'lun clone status' until it was deprecated $cmd = "$cmdname $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /$cmdname:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } if ($blocks_complete == -1) { ExitSoftError("Blocks complete information was not found in output from ($cmd)"); } return $blocks_complete; } ## @name ::filer_lun_clone_create ## @summary Creates a clone of a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $clone_path Required: The valid qtree root and clone name /vol/vol0/clone1 ## @arg $parent_lun_path Required: The valid path to the lun being cloned ## @arg $parent_lun_snapshot_name Required: The snapshot name "nightly0" ## ('vld' is not valid on this call) sub filer_lun_clone_create ($$$$) { my($fc) = shift; my $clone_path = arg_path(shift); # enforce lun_path conventions my $parent_lun_path = arg_path(shift); # enforce lun_path conventions my $parent_lun_snapshot_name = shift; # enforce lun_path conventions my $cmdname = 'lun clone create'; my $cmd = "$cmdname $clone_path -b $parent_lun_path $parent_lun_snapshot_name"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_clone_percentcomplete_get ## @summary Returns the cloning status of the lun in terms of percentage complete. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lunpath Required: The lun_path for this LUN sub filer_lun_clone_percentcomplete_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $percent_complete = ''; my ($blocks_complete,$blocks_total); my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-clone-status-list-info", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq VDISK_ERROR_CLONE_NOT_SPLITTING or $result eq VDISK_ERROR_CLONE_NOT_SPLITTING2) { $percent_complete = 100; } else { if ($text =~ m/(\d+)<\/blocks-completed>/) { $blocks_complete = $1; } else { ExitSoftError("No blocks-complete value returned"); $blocks_complete = 0; } if ($text =~ m/(\d+)<\/blocks-total>/) { $blocks_total = $1; } else { ExitSoftError("No blocks-total value returned"); } if ($blocks_total != 0) { $percent_complete = int( 100 * ($blocks_complete/$blocks_total)); } else { # If there are no total blocks, we are 100% done $percent_complete = 100; } } } else { # Use ONTAP CLI my $cmdname = "lun clone split status"; $cmd = "$cmdname $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { if ($text =~ m/LUN is not being cloned/) { $percent_complete = 100; } elsif ($text =~ m/LUN is not a clone/) { $percent_complete = 100; } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } if ($percent_complete eq '') { my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ m/lun clone split status:\s+Done\s+\d+\s+of\s+\d+\s+blocks\s+\((\d+)\% complete\)/) { $percent_complete = $1; } elsif ($line =~ /$cmdname:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } } if ($percent_complete eq '') { ExitSoftError("Percent complete information was not found in output from ($cmd)"); } } } return $percent_complete; } ## @name ::filer_lun_clone_split_start ## @summary Splits a clone of a LUN from the snapshot ## @arg $fc Required: The filer connection object for the command line interface ## @arg $clone_path Required: The valid qtree root and clone name /vol/vol0/clone1 sub filer_lun_clone_split_start ($$) { my($fc) = shift; my $clone_path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $clone_path =~ s/^['"]//; $clone_path =~ s/['"]$//; my @api_args = ("lun-clone-split-start", "path", $clone_path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun clone split start'; $cmd = "$cmdname $clone_path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ m/$cmdname:/) { if ($text =~ m/Cloning completed on lun/) { # This message appears to announce the cloning completed } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_clone_split_stop ## @summary Stops splitting a clone of a LUN from the snapshot ## @arg $fc Required: The filer connection object for the command line interface ## @arg $clone_path Required: The valid qtree root and clone name /vol/vol0/clone1 sub filer_lun_clone_split_stop ($$) { my($fc) = shift; my $clone_path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $clone_path =~ s/^['"]//; $clone_path =~ s/['"]$//; my @api_args = ("lun-clone-split-stop", "path", $clone_path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun clone split stop'; $cmd = "$cmdname $clone_path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ m/$cmdname:/) { if ($text =~ m/is not being cloned/) { # This message appears to announce the cloning was already completed } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_clone_start ## @summary Start cloning of the specified lun. ## (This function calls the documented lun-clone-start ZAPI, ## yet uses the CLI 'lun clone split start', since 'lun clone start' is deprecated.) ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lunpath Required: The lun_path for this LUN sub filer_lun_clone_start ($$) { filer_lun_clone_split_start(shift,shift); } ## @name ::filer_lun_clone_stop ## @summary Start cloning of the specified lun. ## (This function calls the documented lun-clone-stop ZAPI, ## yet uses the CLI 'lun clone split stop', since 'lun clone stop' is deprecated.) ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lunpath Required: The lun_path for this LUN sub filer_lun_clone_stop ($$) { filer_lun_clone_split_stop(shift,shift); } ## @name ::filer_lun_clone_wait ## @summary Wait for cloning of the specified lun to complete ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lunpath Required: The lun_path for this LUN sub filer_lun_clone_wait ($$;$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $timeout = shift; if (!defined($timeout)) { $timeout = 3600; } my $endtime = time() + $timeout; while (filer_lun_clone_percentcomplete_get($fc,$path) ne '100') { if ($endtime < time()) { ExitError("LUN clone split did not complete in $timeout seconds"); } sleep(20) } } ## @name ::filer_lun_comment_get ## @summary Gets the comment associated with a lun ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_comment_get ($$) { my($fc) = shift; my($path) = shift; $path = arg_path($path); # enforce lun_path conventions my $comment = ''; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-get-comment", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # Parse the returned comment if ($text =~ m/(.*?)<\/comment>/) { $comment = $1; } else { if ($result eq VDISK_ERROR_NO_SUCH_ATTRIBUTE) { # There is no comment for this LUN $result = cmdOkReturn; } else { ExitSoftError("No comment text returned"); } } } else { # Use ONTAP CLI $cmd = "lun comment $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun comment:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\s*$/) { # Looks like a blank line (probably ahead of a vfiler context separator) } elsif ($line =~ /^===== /) { # Looks like a vfiler context separator } elsif ($line =~ /^\s+Comment: /) { # New comment output format has been seen (burt 75312 ) $comment = $line; $comment =~ s/^\s+Comment: (.*)/$1/; # Check comment for inconsistent handling lun_comment_check($comment); } else { # Exact format not defined, apparently a single line of output if defined $comment = $line; # Remove leading 8 characters to get the beginning of the comment $comment =~ s/ (.*)/$1/; # Check comment for inconsistent handling lun_comment_check($comment); if ($comment =~ /\s/) { burt_begin(74854); if ($comment =~ /^".*"$/) { } elsif ($comment =~ /^'.*'$/) { } else { logcomment(["Comment text:",$comment]); ExitSoftError("Comment text with whitespace should have quotes when displayed"); } burt_end(74854); } } } # Strip off double quotes to leave the real comment if ($comment =~ /^".*"$/) { $comment =~ s/^"(.*)"$/$1/; } } if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } if (filer_crosscheck_is_enabled() ) { # lun comment is also exposed by lun show, and lun show -v my %hash = filer_lun_get($fc,$path); if (defined($hash{'Comment'})) { if ($hash{'Comment'} ne $comment) { ExitSoftError("Comment text varies between 'lun show' and 'lun comment' for: $path","lun show: '$hash{'Comment'}'","lun comment: '$comment'"); } } else { if ($comment ne '' && $comment ne '""') { ExitSoftError("No comment returned from 'lun show', yet 'lun comment' shows: '$comment'"); } } } return $comment } ## @name ::filer_lun_comment_set ## @summary Sets the comment for a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $comment Required: The comment value for this LUN sub filer_lun_comment_set ($$$) { my($fc) = shift; my($path) = shift; my($comment) = shift; $path = arg_path($path); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-set-comment", "path", $path, "comment", $comment); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "lun comment $path $comment"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } ## @name ::filer_lun_convert ## @summary Converts an existing 'plain' or dDAFS file into a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $file Required: A file to be converted ## @arg $path Required: The valid lun_path for this LUN sub filer_lun_convert ($$$) { my($fc) = shift; my($file) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_create_from_file($fc,$path,$file); } ## @name ::filer_lun_convert_vld ## @summary Converts an existing VLD into a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $file Required: A VLD to be converted ## @arg $path Required: The valid lun_path for this LUN sub filer_lun_convert_vld ($$$) { my($fc) = shift; my($file) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_create_from_file($fc,$path,$file,'vld'); } ## @name ::filer_lun_create ## @summary Creates a LUN of specified size ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $size Required: A valid size for the lun - byte count (k,m,g,t suffix is OK) ## @arg $ostype Required: 'aix' hpux' 'linux' 'solaris' 'windows' 'netware' 'vmware' ## ostype is mandatory ## ('vld' is not valid on this call) sub filer_lun_create ($$$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $temp_size = shift; # Maintain original parameter sequence my $ostype = arg_ostype(shift); # enforce os_type conventions if ($ostype eq 'vld') { ExitScriptError("Use filer_lun_convert_vld to test a type of 'vld'"); } my($size,$bytes) = arg_lun_size($temp_size,$ostype); my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-create-by-size", "path", $path, "type", $ostype, "size", $bytes); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun create'; $cmd = "$cmdname -s $size -t $ostype $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{timeout => 120}); if ($text =~ m/$cmdname:/) { if ($text =~ m/created a LUN of size/) { # This message appears to announce the size of lun that was created } elsif ($text =~ m/No space left on device/i) { ExitError("Not enough space to create this $size byte LUN ($cmd)"); } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_create_from_file ## @summary Creates a LUN from a file ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $file Required: A snapshot file for the LUN ## @arg $ostype optional: 'image' (default), ## 'aix', hpux', 'linux', 'netware', 'solaris', 'vmware', 'windows' sub filer_lun_create_from_file ($$$;$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($file) = arg_file(shift); my $ostype = arg_ostype(shift); # enforce os_type conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-create-from-file", "path", $path, "type", $ostype, "file-name", $file); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun create'; $cmd = "$cmdname -f $file -t $ostype $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{timeout => 120}); if ($text =~ m/$cmdname:/) { if ($text =~ m/created a LUN of size/) { # This message appears to announce the size of lun that was created } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_create_from_file_with_noreserve ## @summary Creates a LUN from a file ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $file Required: A snapshot file for the LUN ## @arg $ostype optional: 'image' (default), ## 'aix', hpux', 'linux', 'netware', 'solaris', 'vmware', 'windows' sub filer_lun_create_from_file_with_noreserve ($$$;$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($file) = arg_file(shift); my $ostype = arg_ostype(shift); # enforce os_type conventions if ($ostype eq 'vld') { ExitScriptError("Use filer_lun_convert_vld to test a type of 'vld'"); } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-create-from-file", "path", $path, "type", $ostype, "space-reservation-enabled", 'false', "file-name", $file); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun create'; $cmd = "$cmdname -f $file -t $ostype -o noreserve $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{timeout => 120}); if ($text =~ m/$cmdname:/) { if ($text =~ m/created a LUN of size/) { # This message appears to announce the size of lun that was created } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_create_from_snapshot ## @summary Creates a LUN from a LUN within a snapshot ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $snapshot Required: The snapshot_lun_path of the source for the new LUN ## @arg $ostype deprecated: 'image' (default), ## 'aix', hpux', 'linux', 'netware', 'solaris', 'vmware', 'windows' sub filer_lun_create_from_snapshot ($$$;$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($parent_lunpath,$parent_snap,$parent_lun) = arg_snapshot_lun_path2(shift); my $parent_path = arg_path("$parent_lunpath$parent_lun"); # enforce conventional path and add quote. my $ostype = arg_ostype(shift); # enforce os_type conventions if ($ostype eq 'vld') { ExitScriptError("Use filer_lun_convert_vld to test a type of 'vld'"); } my($cmd,$text,$result); my $snapshot = "$parent_lunpath/.snapshot/$parent_snap/$parent_lun"; if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; $snapshot =~ s/^['"]//; $snapshot =~ s/['"]$//; my @api_args = ("lun-create-from-snapshot", "path", $path, "snapshot-lun-path", $snapshot); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname; my $wafl_version = get_wafl_version($fc->hostp()); # Scrimshaw (and pre) doesn't support 'lun clone create' # hence use old convention of '-b' if($wafl_version < WV_ANCHORSTEAM_MIN) { $cmdname = "lun create"; $cmd = "$cmdname -b $snapshot $path"; } else { $cmdname = 'lun clone create'; $cmd = "$cmdname $path -b $parent_path $parent_snap"; } $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{timeout => 120}); if ($text =~ m/$cmdname:/) { if ($text =~ m/created a LUN of size/) { # This message appears to announce the size of lun that was created } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_create_from_snapshot_with_noreserve ## @summary Creates a LUN from a LUN within a snapshot ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $snapshot Required: The snapshot_lun_path of the source for the new LUN ## @arg $ostype deprecated: 'image' (default), ## 'aix', hpux', 'linux', 'netware', 'solaris', 'vmware', 'windows' sub filer_lun_create_from_snapshot_with_noreserve ($$$;$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($parent_lunpath,$parent_snap,$parent_lun) = arg_snapshot_lun_path2(shift); my $ostype = arg_ostype(shift); # enforce os_type conventions if ($ostype eq 'vld') { ExitScriptError("Use filer_lun_convert_vld to test a type of 'vld'"); } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my $snapshot = "$parent_lunpath/.snapshot/$parent_snap/$parent_lun"; $snapshot =~ s/^['"]//; $snapshot =~ s/['"]$//; my @api_args = ("lun-create-from-snapshot", "path", $path, "space-reservation-enabled", 'false', "snapshot-lun-path", $snapshot); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI # New for Anchorsteam # lun clone create [-o noreserve] -b my $cmdname = 'lun clone create'; $cmd = "$cmdname $path -o noreserve -b $parent_lunpath/$parent_lun $parent_snap"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{timeout => 120}); if ($text =~ m/$cmdname:/) { if ($text =~ m/created a LUN of size/) { # This message appears to announce the size of lun that was created } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_create_with_noreserve ## @summary Creates a LUN of specified size without space reservations ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN ## @arg $size optional: A valid size for the lun - byte count (k,m,g,t suffix is OK) ## defaults to the minimum for the ostype ## @arg $ostype optional: 'image' (default), ## 'aix', hpux', 'linux', 'netware', 'solaris', 'vmware', 'windows' sub filer_lun_create_with_noreserve ($$;$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $temp_size = shift; # Maintain original parameter sequence my $ostype = arg_ostype(shift); # enforce os_type conventions if ($ostype eq 'vld') { ExitScriptError("Use filer_lun_convert_vld to test a type of 'vld'"); } my($size,$bytes) = arg_lun_size($temp_size,$ostype); my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-create-by-size", "path", $path, "type", $ostype, "space-reservation-enabled", 'false', "size", $bytes); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun create'; $cmd = "$cmdname -s $size -t $ostype -o noreserve $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{timeout => 120}); if ($text =~ m/$cmdname:/) { if ($text =~ m/created a LUN of size/) { # This message appears to announce the size of lun that was created } else { ExitError("Unexpected message from ($cmd)"); } } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_dbedit_clear ## @summary Clears the contents of vdisk metafiles. ## @arg $fc Required: The filer connection object for the command line interface ## @arg %metafiles Optional: Any of the following: uuid=>1, lunmap=>1, pr=>1, igroup=>1 sub filer_lun_dbedit_clear ($;%) { my ($fc, %metafiles) = @_; my $options="-"; if (defined($metafiles{'uuid'})) { $options .= "u"; } if (defined($metafiles{'lunmap'})) { $options .= "l"; } if (defined($metafiles{'pr'})) { $options .= "p"; } if (defined($metafiles{'igroup'})) { $options .= "g"; } if ($options eq '-') { ExitScriptError("Must specify at least one metafile to clear"); } my($cmd,$text,$result); my $cmdname = "lun dbedit clear"; $cmd = "$cmdname " . $options; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd); if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_dbedit_dump ## @summary Copies the contents of vdisk metafiles to a specified directory. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $dumpPath Required: The filer's directory where metafiles are to be copied ## @arg %metafiles Optional: uuid=>1, lunmap=>1, pr=>1, igroup=>1 sub filer_lun_dbedit_dump ($$;%) { my ($fc, $dumpPath, %metafiles) = @_; my $options="-"; if (defined($metafiles{'uuid'})) { $options .= "u"; } if (defined($metafiles{'lunmap'})) { $options .= "l"; } if (defined($metafiles{'pr'})) { $options .= "p"; } if (defined($metafiles{'igroup'})) { $options .= "g"; } if ($options eq '-') { ExitScriptError("Must specify at least one metafile to dump"); } $options .= " "; my($cmd,$text,$result); my $cmdname = "lun dbedit dump"; $cmd = "$cmdname " . $options . $dumpPath; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd); if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_dbedit_delete_by_path ## @summary Delete a vdisk metafile record given a lun path ## @arg $fc Required: The filer connection object for the command line interface ## @arg $metafile Required: One of three possible values: lunmap, uuid or pr ## @arg $lunPath Required: The path of the LUN affected. sub filer_lun_dbedit_delete_by_path ($$$) { my ($fc, $metafile, $lunPath) = @_; my $option="-"; if ($metafile eq "uuid") { $option .= "u"; } elsif ($metafile eq "lunmap") { $option .= "l"; } elsif ($metafile eq "pr") { $option .= "p"; } else { ExitScriptError("Must specify at least one of uuid, lunmap, pr to delete"); } $option .= " "; my($cmd,$text,$result); my $cmdname = "lun dbedit delete"; $cmd = "$cmdname " . $option . $lunPath; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd); if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_dbedit_delete_by_index ## @summary Delete a vdisk metafile record given its index (0-based) ## @arg $fc Required: The filer connection object for the command line interface ## @arg $metafile Required: One of four possible values: lunmap, uuid, igroup or pr ## @arg $lunPath Required: The path of the LUN affected. sub filer_lun_dbedit_delete_by_index ($$$) { my ($fc, $metafile, $index) = @_; my $option="-"; if ($metafile eq "uuid") { $option .= "u"; } elsif ($metafile eq "lunmap") { $option .= "l"; } elsif ($metafile eq "pr") { $option .= "p"; } elsif ($metafile eq "igroup") { $option .= "g"; } else { ExitScriptError("Must specify at least one of uuid, lunmap, pr, igroup to delete"); } $option .= "i "; my($cmd,$text,$result); my $cmdname = "lun dbedit delete"; $cmd = "$cmdname " . $option . $index; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd); if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_destroy ## @summary Destroys a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid lun_path for this LUN sub filer_lun_destroy ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-destroy", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun destroy'; $cmd = "$cmdname $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ /\[wafl_lopri:warning\]: |\[vdisk_admin:notice\]:/) { ErrorSoftError('-burt',67376,"Extraneous messages seen with ($cmd)"); } # Messages may have this format: #Fri Feb 13 09:46:25 GMT [rtpqa-fas940-4: lun.destroy:warning]: LUN /vol/sledgehammer/nate_rtpqa-sun1000-1-1_lun37 destroyed if ($text =~ m/$cmdname:/) { if ($text =~ m/No such LUN exists/) { ExitSoftError("LUN cannot be destroyed, it does not exist"); # Override result, so additional error message will be skipped $result = cmdOkReturn; } else { if (filer_lun_exists($fc,$path)) { ExitError("Unexpected message from ($cmd)"); } else { ExitSoftError("Unexpected message from ($cmd)"); } } } } if ($result ne cmdOkReturn and $result ne VDISK_ERROR_NO_SUCH_VDISK) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { if (filer_lun_exists($fc,$path)) { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } } ## @name ::filer_lun_df ## @summary Returns formated contents of a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $block_number Optional: ## @arg $block_offset Optional: sub filer_lun_df ($$;$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $block_number = shift; my $block_offset = shift; if (!defined($block_number)) { $block_number = ''; } if (!defined($block_offset)) { $block_offset = ''; } if (($block_number eq '' and $block_offset ne '') or ($block_number ne '' and $block_offset eq '')) { ExitScriptError("Both block_number and block_offset must be specified if either one is defined"); } my $cmdname = 'lun df'; my $cmd = "$cmdname $path $block_number $block_offset"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/$cmdname:/) { ExitSoftError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } return $text; } ## @name ::filer_lun_df_hex ## @summary Returns formated contents of a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $block_number Optional: ## @arg $block_offset Optional: sub filer_lun_df_hex ($$;$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $block_number = shift; my $block_offset = shift; if (!defined($block_number)) { $block_number = 0; } if (!defined($block_offset)) { $block_offset = 0; } if (($block_number eq '' and $block_offset ne '') or ($block_number ne '' and $block_offset eq '')) { ExitScriptError("Both block_number and block_offset must be specified if either one is defined"); } my $cmdname = 'lun df'; my $cmd = "$cmdname $path -h $block_number $block_offset"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/$cmdname:/) { ExitSoftError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { if ($result eq cmdBadUsageReturn) { my $version = filer_version_get($fc); if ($version =~ m/d>/) { ExitSoftError("The -h option should have been accepted from ($cmd)"); } else { ExitScriptError("The -h option is only valid with a debug kernel, not $version"); } } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } return $text; } ## @name ::filer_lun_df_verbose ## @summary Returns formated contents of a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $block_number Optional: ## @arg $block_offset Optional: sub filer_lun_df_verbose ($$;$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $block_number = shift; my $block_offset = shift; if (!defined($block_number)) { $block_number = ''; } if (!defined($block_offset)) { $block_offset = ''; } if (($block_number eq '' and $block_offset ne '') or ($block_number ne '' and $block_offset eq '')) { ExitScriptError("Both block_number and block_offset must be specified if either one is defined"); } my $cmdname = 'lun df'; my $cmd = "$cmdname $path -v $block_number $block_offset"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/$cmdname:/) { ExitSoftError("Unexpected message from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } return $text; } ## @name ::filer_lun_exists ## @summary Returns 1 if the lun exists ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_exists ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-list-info", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result eq cmdOkReturn) { return 1; } elsif ($result eq VDISK_ERROR_NO_SUCH_VDISK) { return 0; }else { ExitSoftError("Unexpected return code: $result from ($cmd)"); } } else { # Use ONTAP CLI $cmd = "lun show $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $lun = ""; my @info = filer_cli_split($text); foreach my $line (@info) { if ( $line =~ /lun show: No such LUN exists/ ) { return 0; } elsif ( ($line =~ /lun show\s?:/) || ($line =~ /usage:/) ) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /\/vol\//) { return 1; } } } return 0; # lun was not found } ## @name ::filer_lun_geometry_get ## @summary Returns the geometry of a lun ## @arg $fc Required: The filer connection object for the command line interface ## @arg $lun_path Required: The path to this LUN sub filer_lun_geometry_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my %geometry; my %geometry_keys = ( 'bytes-per-sector' => 'bytes/sector', 'cylinders' => 'cylinders', 'max-resize-size' => 'max resize size', 'sectors-per-track' => 'sectors/track', 'size' => 'device size', 'tracks-per-cylinder' => 'tracks/cylinder', # The following are seen with the CLI, but not documented for ZAPI 'cylinder-size' => 'cylinder size', 'sectors' => 'sectors', 'sectors-per-cylinder' => 'sectors/cylinder', ); my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-get-geometry", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); foreach my $key (keys %geometry_keys) { if ($text =~ m/<$key>(\d+)<\/$key>/) { $geometry{$key} = $1; } } } else { # Use ONTAP CLI $cmd = "lun geometry $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($result eq cmdOkReturn) { my @info = filer_cli_split($text); # SCSI Disk Geometry: # 512 bytes/sector # 128 sectors/track # 16 tracks/cylinder (heads) # 2048 sectors/cylinder # 8 cylinders # 16384 sectors # 1048576 cylinder size (1 MB) # 8388608 device size (8 MB) # 68718428160 max resize size (65535 MB) foreach my $key (keys %geometry_keys) { my $meta_search = quotemeta($geometry_keys{$key}); if ($text =~ m/\s+(\d+)\s+$meta_search/) { $geometry{$key} = $1; } } } } return %geometry; } ## @name ::filer_lun_get ## @summary Returns a hash of attributes for a lun. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $cmd = "lun show -v $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my %LunData; my @info = filer_cli_split($text); foreach my $line (@info) { my $value = $line; if (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif ($line =~ /\s+(\/vol\/\S+)\s+(\S+)\s+\((\d+)\)\s+\(\S+,\s+(\w+)[,\)]/) { # \t/vol/sledgehammer/pcmds_lun_create/e 4m (4194304) (r/w, online)\n #\t/vol/sledgehammer/pcmds_lun_create/offline 7.8g (8388608000) (r/w, online) $LunData{'Lun Name'} = $1; $LunData{'Size And Unit'} = $2; $LunData{'Size'} = $3; $LunData{'State'} = $4; } elsif ($value =~ /Backed By:/i) { $value =~ s/\s*Backed By:\s+//i; $LunData{'Backed By'} = $value; } elsif ($value =~ /Comment: /) { $value =~ s/\s*Comment:\s+"(.*)"$/$1/; $LunData{'Comment'} = $value; # Check comment for inconsistent handling lun_comment_check('"'.$value.'"'); } elsif ($value =~ /\s+Exports:\s+/) { $value =~ s/.*Exports:\s+(.*)/$1/; ExitSoftError("All references to lun 'exports' must be changed to lun 'maps'"); $LunData{'Maps'} = $value; } elsif ($value =~ /Maps:/) { $value =~ s/.*Maps:\s+(.*)/$1/; $LunData{'Maps'} = $value; } elsif ($value =~ /Multiprotocol Type: /) { $value = $line; $value =~ s/\s*Multiprotocol Type:\s+(.*)$/$1/; $LunData{'Multiprotocol Type'} = $value; } elsif ($value =~ /Serial/) { $value =~ s/\s*Serial#:\s+(.*)/$1/; $LunData{'Serial'} = $value; } elsif ($value =~ /Share/) { $value =~ s/Share: "(.*)".*/$1/; $LunData{'Share'} = $value; } elsif ($line =~ /Exports/) { my @exports = split(/\s+/, $line); my @export = splice(@exports, 1); $LunData{'Exports'} = "@export"; } elsif ($value =~ /Space Reservation: /) { $value =~ s/\s*Space Reservation:\s+(.*)$/$1/; $LunData{'Space Reservation'} = $value; } elsif ($value =~ /\s*(.*):\s+(.*)/) { $LunData{$1} = $2; } else { # Exact format of other output is not known } } return %LunData; } ## @name ::filer_lun_help ## @summary Verifies help text appears for lun subcommands ## @description After verifying expected text, returns the help text. ## This script also verifies that advanced, test,and diag commands do not ## have help text in the admin priviledge. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $subcommand Required: The subcommand that help is to be displayed for ## @arg @expected a list of expected values each passed as a regular expression sub filer_lun_help ($$;@) { my($fc) = shift; my($subcommand) = shift; my(@expected) = @_; my $need_to_try_admin = 0; # flag to verify help is NOT present with admin priv my $help_required = 1; # help is not required for test mode commands? my $cmd = "lun help $subcommand"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result); my $conntype = $fc->{conntype}; if (($subcommand =~ /^aluadb$|^attribute$|^config$|^dbedit$|^df$|^geometry$|^hist$|^rescan$|^select$/) ) { ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok',allowerror=>1}); $need_to_try_admin = 1; # Verify this command is not shown in admin mode } elsif ($subcommand =~ /^vtoc$/) { ($text,$result) = filer_cli_test_say($fc,$cmd,{retry=>'ok',allowerror=>1}); $help_required = 0; $need_to_try_admin = 1; # Verify this command is not shown in admin mode # priv level of 'lun config' has been changed to admin in tsingtao # } elsif ($subcommand =~ /^config_check$/) { # ($text,$result) = filer_cli_advanced_say($fc,$cmd,{retry=>'ok',allowerror=>1}); # $need_to_try_admin = 1; # Verify this command is not shown in admin mode } elsif ($subcommand =~ /serial|setup/ && $conntype =~ /rsh/) { # These require advanced if connection is rsh ($text,$result) = filer_cli_advanced_say($fc,$cmd,{retry=>'ok',allowerror=>1}); $need_to_try_admin = 1; # Verify this command is not shown in admin mode } else { # All other subcommands are valid from priv set admin ($text,$result) = filer_cli_admin_say($fc,$cmd,{retry=>'ok',allowerror=>1}); } if ($result eq cmdDefaultReturn) { # Unfortunately, these commands still return the default return code } if ($subcommand =~ /\s+/) { # An attempt to have two subcommands or a sub-subcommand # all subcommands should be displayed $subcommand =~ s/(.*)\s.*/$1/; } my $foundproto = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /vdisk/i) { ExitSoftError("A reference to 'vdisk' was found in ($cmd) output: $line"); } if ($line =~ /iniator/i) { ExitSoftError('-burt',75722,"A misspelling 'iniator' was found in ($cmd) output: $line"); } if ($line =~ /nospacereserve/i) { ExitSoftError('-burt',80398,"An old keyword 'nospacereserve' was found in ($cmd) output: $line"); } if ($line =~ / unrecognized command/) { if ($subcommand eq 'clone') { ExitSoftError('-burt',104094,"Subcommand '$subcommand' is not recognized by 'lun help'"); } elsif ($subcommand eq 'config_check' and $cmd =~ m/vfiler run/ and $cmd !~ m/run vfiler0/) { burt_fixed(104671); } else { if ($help_required) { ExitSoftError("Subcommand '$subcommand' is not recognized by 'lun help'"); } else { logcomment("Subcommand '$subcommand' is not recognized by 'lun help' under priv test"); } } $foundproto = 2; # This skips extra error for missing prototype if ($subcommand =~ /serial|setup/) { burt_found(68909); } } elsif ($line =~ /lun $subcommand/) { $foundproto = 1; # Exact format of success message is not defined } } if ($foundproto eq 0) { ExitSoftError("No prototype statement found for '$cmd'"); } my @notseen = (); foreach my $expected (@expected) { if (0 == grep { /$expected/ } @info) { if ($expected =~ m/lun_path/ and 0 < grep { /[ <\[]lunpath/ } @info) { ExitSoftError('-burt',132624,"The argument 'lun_path' is preferred over lunpath for consistency"); } else { push(@notseen,$expected); } } } if (@notseen >0 and $foundproto eq 1) { my $not_seen = join("\n",@notseen); ExitSoftError("Missing expected help text output: '$not_seen'"); } # Now verify that special commands not available in admin mode are not shown # in a help request made in that mode if ($need_to_try_admin) { my ($admin_text,$result) = filer_cli_admin_say($fc,$cmd,{retry=>'ok',allowerror=>1}); if ($admin_text !~ / unrecognized command/) { ExitSoftError("This command should not have help from the admin priviledge: $cmd"); } } return $text; } ## @name ::filer_lun_igroups_get ## @summary Returns array of initiator groups mapped to the specified lun. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for an existing LUN sub filer_lun_igroups_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my @igroups = (); my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-map-list-info", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); while ($text =~ m/(\S+?)<\/initiator-group-name>/) { push(@igroups,$1); $text =~ s/\S+?<\/initiator-group-name>//; } } else { # Use ONTAP CLI $cmd = "lun show -m $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $dataok = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun show:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\S+\s+\S+\s+\d+/ ) { # path initiator_group LUN $line =~ s/^\S+\s+(\S+).*/$1/; push(@igroups,$line); } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif ($line =~ /^\s+\S+\s+\d+/ ) { # whitespace initiator_group LUN $line =~ s/^\s+(\S+)\s+.*/$1/; push(@igroups,$line); } else { print("Unrecognized 'lun show -m' output: '$line'"); } } } return @igroups; } ## @name ::filer_lun_is_mapped ## @summary Returns 1 if the lun is mapped to any initiator group. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_is_mapped ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-map-list-info", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(\S+?)<\/initiator-group-name>/) { return 1; } } else { # Use ONTAP CLI $cmd = "lun show $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { # Not mapped: # /vol/vol0/ncmds_lun2/defined1 8m (8388608) (r/w, online) # Mapped: # /vol/vol0/cpq330lun0 40m (41943040) (r/w, online, mapped) if ($line =~ /lun show:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { # vfiler header lines } elsif ($line =~ /\(\d+\)\s+\(.*mapped.*\)/ ) { return 1; } elsif ($line =~ /\(\d+\)\s+\(.*\)/ ) { return 0; # No 'mapped' text, must be unmapped } else { print("Unrecognized '$cmd' output: '$line'"); } } } return 0; } ## @name ::filer_lun_is_online ## @summary Returns 1 if the lun is online. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_is_online ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my %HashOfHashes = filer_lun_get($fc,$path); if ($HashOfHashes{'State'} eq 'online' ) { return 1 } return 0; } ## @name ::filer_lun_map ## @summary Enables target mode access to the LUN ## @description This function maps access to a lun path ## for a specified initiator group ## to a specified LUN number. ## @usage ## use the filer_lun_map_low function ## to have the lun number assigned to the lowest unassigned value ## for the specifed initiator group. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $igroup Required: A valid initiator group ## @arg $lun Required: A LUN for this vdisk ## @see-also ::filer_lun_map_low ## @see-also ::filer_lun_unmap sub filer_lun_map ($$$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($igroup) = shift; my($lun) = shift; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-map", "path", $path, "initiator-group", $igroup, "lun-id", $lun); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { if ($result eq -99) { # what is the real VDISK_ERROR_INITGROUP_HAS_VDISK value?? } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } else { # Use ONTAP CLI $cmd = "lun map $path $igroup $lun"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun map: Space reservation not enabled for volume/) { ExitError('-burt',70386,"Space reservations should not be required to lun map ($cmd): $line"); } elsif ($line =~ /lun map\s?: LUN already mapped to this group/) { # The LUN is already mapped to this igroup, use that map # (Do we want to make sure the same LUN id was given??) last; } elsif ($line =~ /lun map\s?:/) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined # This message should announce the lun that was assigned. # Although such behavior is not specified. } } } } ## @name ::filer_lun_map_low ## @summary Enables target mode access to the LUN at the lowest available LUN for the initiator group ## @description Returns the LUN assigned or undef ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $igroup Required: A valid initiator group ## @see-also ::filer_lun_map ## @see-also ::filer_lun_unmap sub filer_lun_map_low ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($igroup) = shift; my $lun = undef; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-map", "path", $path, "initiator-group", $igroup); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($result ne cmdOkReturn) { if ($result ne '99999') { # what is the real VDISK_ERROR_INITGROUP_HAS_VDISK value?? ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } if ($text =~ m/(\d+)<\/lun-id-assigned>/) { $lun = $1; } } else { # Use ONTAP CLI $cmd = "lun map $path $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /auto-assigned .*=(\d+)/) { $lun = $1; last; } elsif ($line =~ /lun map\s?:/) { ExitError("Unexpected message from ($cmd): $line"); } } } return $lun; } ## @name ::filer_lun_maps_by_igroup_get ## @summary Returns a hash-of-hashes containing lun mapping information indexed by igroup name and either LUN ID or LUN path ## @arg CONN Required: The filer connection object for CLI access ## @arg INDEX_BY Required: Hash the lun maps by 'id' or 'path' # TODO - include igroups with no mappings? sub filer_lun_maps_by_igroup_get (%) { my %opts = @_; my %results = {}; if(not (exists($opts{CONN}) and exists($opts{INDEX_BY}))){ ExitSoftError("Missing required arguments"); } my $cmd = "lun show -m"; $cmd = filer_vfiler_command($opts{CONN},$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($opts{CONN},$cmd,{retry=>'ok'}); foreach my $line (filer_cli_split($text)) { if ($line =~ /^LUN Path/i) { next; } elsif ($line =~ /^---/) { next; } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^(\/vol\/[\w\-\/])\s+([\w\-])\s+(\d+)\s(\w)$/) { my $lun_path = $1; my $igroup = $2; my $lunid = $3; my $proto = $4; # Initialize the next sub-hash if necessary if(not exists($results{$igroup})){ $results{$igroup} = {'protocol' => $proto, 'lun_map' => {}}; } if ($opts{INDEX_BY} =~ /id/i){ $results{$igroup}{'lun_map'}{$lunid} = $lun_path; } elsif ($opts{INDEX_BY} =~ /path/i){ $results{$igroup}{'lun_map'}{$lun_path} = $lunid; } else { # Invalid index option! ExitSoftError("Invalid INDEX_BY option: $opts{INDEX_BY}"); } } } # end foreach return \%results; } ## @name ::filer_lun_maxsize_get ## @summary Returns the maximum size of a new lun on the specified volume or qtree ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root for a new LUN ## @arg $ostype Optional: The desired os_type for a new LUN sub filer_lun_maxsize_get ($$;$) { my($fc) = shift; my $root = shift; my $ostype = shift; if (!defined($ostype)) { $ostype = 'image'; # new value with Anchorsteam } my $size = -1; # Max Size could not be determined my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $root =~ s/^['"]//; $root =~ s/['"]$//; my @api_args = ("lun-get-maxsize", "path", $root, "type", $ostype); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(\d+)<\/with-snapshot-reserve>/) { $size = $1; } else { ExitSoftError("Unable to get the LUN maximum size"); } } else { # Use ONTAP CLI # Add quotes if the lun_path has special characters but was not quoted if ($root =~ /#|\s/ and $root !~ m/^['"]/ and $root !~ m/["']$/) { $root = "'".$root."'"; } $cmd = "lun maxsize $root"; # NOTE: 'lun maxsize' is not listed as vfilerized #$cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($result eq cmdOkReturn) { my $found_ostype = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /$ostype/i) { $found_ostype = 1; next; } if ($found_ostype) { if ($line =~ m/With\s.*\(\d+\)/) { $size = $line; $size =~ s/.*\((\d+)\).*/$1/; last; } elsif ($line =~ m/With\s.* no space/) { $size = 0; last; } } } } } if ($size eq '-1') { logresult('WARN',"Could not determine maximum lun size for OS type $ostype"); } return $size; } ## @name ::filer_lun_maxsize_with_noreserve_get ## @summary Returns the maximum size of a new lun on the specified volume or qtree ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $ostype Optional: The desired os_type for a new LUN sub filer_lun_maxsize_with_noreserve_get ($$;$) { my($fc) = shift; my $root = shift; my $ostype = shift; if (!defined($ostype)) { $ostype = 'image'; # new value with Anchorsteam } my $size = -1; # Max Size could not be determined my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $root =~ s/^['"]//; $root =~ s/['"]$//; my @api_args = ("lun-get-maxsize", "path", $root, "type", $ostype); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(\d+)<\/without-snapshot-reserve>/) { $size = $1; } else { ExitSoftError("Unable to get the LUN maximum size without snapshot reserve"); } } else { # Use ONTAP CLI $cmd = "lun maxsize $root"; ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); if ($result eq cmdOkReturn) { my $found_ostype = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /$ostype/) { $found_ostype = 1; next; } if ($found_ostype) { if ($line =~ m/Without.*\(\d+\)/) { $size = $line; $size =~ s/.*\((\d+)\).*/$1/; last; } } } } } if ($size == -1) { logresult('WARN',"Could not determine maximum lun size"); } return $size; } ## @name ::filer_lun_move ## @summary Renames a lun, while keeping it in the same qtree. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $frompath Required: The initial qtree root and name for this LUN ## @arg $topath Required: The same qtree root and new name for this LUN sub filer_lun_move ($$$) { my($fc) = shift; my $frompath = arg_path(shift); # enforce lun_path conventions my $topath = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $frompath =~ s/^['"]//; $frompath =~ s/['"]$//; $topath =~ s/^['"]//; $topath =~ s/['"]$//; my @api_args = ("lun-move", "path", $frompath, "new-path", $topath); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun move'; $cmd = "$cmdname $frompath $topath"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun move\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined } } if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_offline ## @summary Disables block protocol access to a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_offline ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-offline", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun offline'; $cmd = "$cmdname $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if($text =~ m/LUN is not currently online/) { $result = 0; } elsif ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_online ## @summary Re-enables block protocol access to a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_online ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-online", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun online'; $cmd = "$cmdname $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($text =~ m/$cmdname:/) { ExitError("Unexpected message from ($cmd)"); } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_serial_get ## @summary Gets the serial number of a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_serial_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $serial = ''; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-get-serial-number", "path", $path); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(\S+)<\/serial-number>/) { $serial = $1; } else { ExitSoftError("Unable to get the LUN serial number"); } } else { # Use ONTAP CLI $cmd = "lun serial $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context # 30 seconds failed 2005-03-16 ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok',timeout=>60,allowerror=>1}); if ($result eq cmdNotFoundReturn) { ExitSoftError('-burt',73350,"'lun serial' command has not been implemented yet."); } else { my $foundprompt = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun serial\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format not defined $serial = $line; $serial =~ s/.*Serial#:\s+(.*)/$1/; $foundprompt = 1; } } if (!$foundprompt) { ExitSoftError("No serial number returned from ($cmd)"); } else { if (filer_crosscheck_is_enabled() ) { # lun serial is also exposed by lun show, and lun show -v my %hash = filer_lun_get($fc,$path); if (defined($hash{'Serial'})) { if ($hash{'Serial'} ne $serial) { ExitSoftError("Lun serial number cross verification error. lun serial: '$serial', lun show: '$hash{'Serial'}'"); } } else { if ($serial ne '') { ExitSoftError("No serial number returned from 'lun show', yet 'lun serial' shows: '$serial'"); } } } } } } return $serial; } ## @name ::filer_lun_serial_set ## @summary Sets the serial number of a LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $serial Required: The serial number for this LUN sub filer_lun_serial_set ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($serial) = shift; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-set-serial-number", "path", $path, "serial-number", $serial); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "lun serial $path $serial"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_lun_share ## @summary Allows file-level read operations on the LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $access Required: The 'read' 'write' 'all' or 'none' access to be requested ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_share ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($access) = shift; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-set-share", "path", $path, "share-type", $access); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "lun share $path $access"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun share\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format not defined } } } return; } ## @name ::filer_lun_share_all ## @summary Allows file-level read operations on the LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_share_all ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_share($fc,$path,'all'); } ## @name ::filer_lun_share_none ## @summary Allows file-level read operations on the LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_share_none ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_share($fc,$path,'none'); } ## @name ::filer_lun_share_read ## @summary Allows file-level read operations on the LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_share_read ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_share($fc,$path,'read'); } ## @name ::filer_lun_share_write ## @summary Allows file-level read operations on the LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_share_write ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions filer_lun_share($fc,$path,'write'); } ## @name ::filer_lun_show_get ## @summary Returns a hash of hashes for each LUN with lun show output fields ## @arg $fc Required: The filer connection object for the command line interface sub filer_lun_show_get ($) { my($fc) = shift; my %HashOfHashes; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @lun_paths = filer_luns_get($fc); foreach my $path (@lun_paths) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; foreach my $name (@lun_attribute_names) { my @api_args = ("lun-get-attribute", "path", $path, "name",$name); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); if ($text =~ m/(.*?)<\/value>/) { $HashOfHashes{$path}{$name} = $1; } } } } else { # Use ONTAP CLI $cmd = "lun show -v"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $lun = ""; my @info = filer_cli_split($text); foreach my $line (@info) { # '@Anvil@ConvertedVLDName', # '@Anvil@CreatedByVSS', # '@Anvil@FSConsistent'. # '@Anvil@RWVLDSnap', # '@Anvil@SFSROnline', # '@Anvil@SFSRStatus', # '@Anvil@SnapMirSFSR', # '@Anvil@VdiskTemp', # 'clone', # 'comment', # 'custom', # 'cylinder_size', # 'enabled', # 'extent_size', # 'host_stamp', # 'nvfail', # 'old_size', # 'path_last', # 'pserial', # 'select', # 'serial', # 'sfsr_clone_restore', # 'share', # 'snapshot', # 'snapshot_path_last', # 'type' if (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { next; # vfiler header lines } elsif ($line =~ /\s+(\/vol\/\S+)\s+\S+\s+\((\d+)\).*/) { $lun = $1; $HashOfHashes{$lun}{'Lun Name'} = $lun; $HashOfHashes{$lun}{'Size'} = $2; if ($line =~ /\s+\/vol\/\S+\s+\S+\s+\(\d+\)\s+\(\s*(\S+),\s*(\S+)[,\)]/) { # (r/o, online, mapped) $HashOfHashes{$lun}{'ReadWrite'} = $1; $HashOfHashes{$lun}{'Online'} = $2; } } elsif ($line =~ /Backed By:\s+(.*)/i) { $HashOfHashes{$lun}{'Backed By'} = $1; } elsif ($line =~ /\s*Comment:\s+"(.*)"$/) { $HashOfHashes{$lun}{'Comment'} = $1; } elsif ($line =~ /.*Maps:\s+(.*)/) { $HashOfHashes{$lun}{'Maps'} = $1; } elsif ($line =~ /\s*Multiprotocol Type:\s+(.*)$/) { $HashOfHashes{$lun}{'Multiprotocol Type'} = $1; } elsif ($line =~ /Serial#:\s+(.*)/) { $HashOfHashes{$lun}{'Serial'} = $1; } elsif ($line =~ /\s*Share:\s+(.*)/) { $HashOfHashes{$lun}{'Share'} = $1; } elsif ($line =~ /\s*Space Reservation:\s+(.*)$/) { $HashOfHashes{$lun}{'Space Reservation'} = $1; } else { # Exact format of success message is not defined, but must be validated } } } return %HashOfHashes; } ## @name ::filer_lun_size_change ## @summary Resizes a LUN to a new size ## @description This is the easy function for a test script to modify a lun size. ## It applies the '-f' flag when the change would be to decrease the existing size. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $size Required: A valid size for the LUN - byte count (c,w,b,k,m,g,t suffix is OK) sub filer_lun_size_change ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($size,$bytes) = arg_lun_size(shift); my($text,$result); my $now_size =filer_lun_size_get($fc,$path); if ($now_size <= $bytes) { ($text,$result) = filer_lun_size_increase($fc,$path,$size); } else { ($text,$result) = filer_lun_size_decrease($fc,$path,$size); } if (wantarray) { return ($text,$result); } else { return $text; } } ## @name ::filer_lun_size_decrease ## @summary Resizes a LUN to a smaller size ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $size Required: A valid size for the LUN - byte count (c,w,b,k,m,g,t suffix is OK) sub filer_lun_size_decrease ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($size,$bytes) = arg_lun_size(shift); ## Burt 65978 should identify return codes ## Burt 65979 should identify expected message text my $cmd = "lun resize -f $path $size"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd); if (wantarray) { return ($text,$result); } else { return $text; } } ## @name ::filer_lun_size_get ## @summary Returns the byte count size of the specified lun ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_size_get ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my %LunData = filer_lun_get($fc,$path); return $LunData{'Size'}; } ## @name ::filer_lun_size_increase ## @summary Resizes a LUN to a larger size ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $size Required: A valid size for the LUN - byte count (c,w,b,k,m,g,t suffix is OK) sub filer_lun_size_increase ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($size,$bytes) = arg_lun_size(shift); ## Burt 65978 should identify return codes ## Burt 65979 should identify expected message text my $cmd = "lun resize $path $size"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result) = filer_cli_say($fc,$cmd); if (wantarray) { return ($text,$result); } else { return $text; } } ## @name ::filer_lun_stats_get ## @summary Returns lun statistics for the filer. ## @description ## Returns a list of read kilobytes,write kilobytes, read I/O operations, write I/O operations, total luns. ## ## @arg $fc Required: The filer connection object where this function is executed ## @arg $all Optional: if 1, return all stats, if 0, return only non-zero stats ## default: 0 ## @arg $path Optional: The valid qtree root and name for a lun, defaults to all sub filer_lun_stats_get ($;$$) { my $fc = shift; my $all = shift; my $path = shift; my $timeout = 60; my $show_all_luns = ""; if (!defined($path)) { $path = ''; } if (!defined($all)) { $all = 0; } if ($all) { $show_all_luns = " -a "; # There could be many LUNs which would cause this command to time out $timeout = 300; } if ($path ne '') { $path = arg_path($path); # enforce lun_path conventions $show_all_luns = ""; } my @stats = (0,0,0,0,0); my ($key, $value); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("lun-stats-list-info"); push (@api_args, "path", $path) if ($path ne ''); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # get stats info my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; logcomment("key: $key, value: $value"); # parse compatible info w/ CLI if($key eq 'read-blocks') { $stats[0] = $value; } if($key eq 'write-blocks') { $stats[1] = $value; } if($key eq 'read-ops') { $stats[2] = $value; } if($key eq 'read-ops') { $stats[3] = $value; } if ($key eq 'path') { $stats[4]++; } } } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "lun stats $show_all_luns $path"; ($text,$result) = filer_cli_say($fc,$cmd,{timeout=>$timeout,retry=>'ok'}); if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code '$result' from ($cmd)"); } my $got_heading = 0; my @info = filer_cli_split($text); foreach my $line (@info) { # Remove syslog messages $line = filer_cli_clean($line); if ($line =~ /\d+\s+\d+\s+\d+/) { if ($got_heading) { $line =~ s/^\s+//; $line =~ s/\s+/ /g; my @lun_stats = split(' ',$line); if (@lun_stats > 4) { ExitSoftError("More than 4 ($cmd) values returned: '$line'"); } elsif (@lun_stats < 4) { ExitSoftError("Less than 4 ($cmd) values returned: '$line'"); } else { $stats[0] += $lun_stats[0]; $stats[1] += $lun_stats[1]; $stats[2] += $lun_stats[2]; $stats[3] += $lun_stats[3]; $stats[4]++; # Increment the count of luns } } else { logcomment("Unrecognized header output found from ($cmd): '$line'"); } } elsif ($line =~ /Read \(kbytes\)\s+Write \(kbytes\)\s+Read Ops\s+Write Ops/) { $got_heading++; } elsif ($line eq '' or $line =~ m/===== /) { next; # Allow vfiler context headers } elsif ($line =~ /lun stats:/) { ExitSoftError("Unexpected message from ($cmd): '$line'"); } else { if ($path ne '') { if ($line !~ /^\s+$path\s*/) { ExitSoftError("Unrecognized output found from ($cmd): '$line'"); } } else { if ($line !~ /^\s+\S+\s*\(.*\)$/) { ExitSoftError("Unrecognized lun path output found from ($cmd): '$line'"); } } } } if ($got_heading == 0) { ExitSoftError("No heading detected from ($cmd)"); } elsif ($got_heading != $stats[4] ) { ExitSoftError("Heading count ($got_heading) is not equal to the count of statistics lines ($stats[4])"); } } return @stats; } ## @name ::filer_lun_stats_get_extended ## @summary Returns extended lun statistics for the filer. ## @description ## Returns a list of ## read kilobytes, ## write kilobytes, ## read I/O operations, ## write I/O operations, ## other I/O operations, ## QFulls ## Partner Ops ## Partner KBytes. ## ## @arg $fc Required: The filer connection object where this function is executed ## @arg $all Required: Boolean, if true, return all stats, if false, return only non-zero stats ## @arg $path Optional: The valid qtree root and name for a lun, defaults to all sub filer_lun_stats_get_extended ($$;$) { my $fc = shift; my $all = shift; my $path = shift; my $timeout = 60; my $show_all_luns = ""; if (!defined($path)) { $path = ''; } if ($all) { $show_all_luns = " -a "; # There could be many LUNs which would cause this command to time out $timeout = 300; } if ($path ne '') { $path = arg_path($path); # enforce lun_path conventions $show_all_luns = ""; } my @stats = (0,0,0,0,0,0,0,0,0); my ($key, $value); my ($cmd, $result, $text); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate( $fc)) { my @api_args = ("lun-stats-list-info"); push (@api_args, "path", $path) if ($path ne ''); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # check result if ($result ne cmdOkReturn) { ExitError("Unexpected return code ($result) from zapi command '$cmd'"); } # get extended stats info my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; logcomment("key: $key, value: $value"); if($key eq 'read-blocks') { $stats[0] = $value; } if($key eq 'write-blocks') { $stats[1] = $value; } if($key eq 'read-ops') { $stats[2] = $value; } if($key eq 'read-ops') { $stats[3] = $value; } if($key eq 'other-ops') { $stats[4] = $value; } if ($key eq 'path') { $stats[7]++; } } } } else { # Use ONTAP CLI $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context $cmd = "lun stats -o $show_all_luns $path"; ($text,$result) = filer_cli_say($fc,$cmd,{timeout=>$timeout,retry=>'ok'}); # Initialize each output column counter and an extra one for the number of LUNs my $expected_columns = @stats-1; if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code '$result' from ($cmd)"); } my $got_heading = 0; my @info = filer_cli_split($text); foreach my $line (@info) { # Remove syslog messages $line = filer_cli_clean($line); if ($line =~ /\d+\s+\d+\s+\d+\s+\d+\s+\d+/) { # This appears to be a output line with data if ($got_heading) { $line =~ s/^\s+//; $line =~ s/\s+/ /g; # Merge known multi-word headings $line =~ s/Partner /Partner/g; $line =~ s/ Ops/Ops/g; $line =~ s/ \(s/\(/g; my @lun_stats = split(' ',$line); if (@lun_stats > $expected_columns) { ExitSoftError("More than $expected_columns ($cmd) values returned: '$line'"); } elsif (@lun_stats < $expected_columns) { ExitSoftError("Less than $expected_columns ($cmd) values returned: '$line'"); } else { my $loop = $expected_columns; while ($loop>=1) { $loop--; $stats[$loop] += $lun_stats[$loop]; } $stats[$expected_columns]++; # Increment the count of luns } } else { logcomment("Unrecognized header output found from ($cmd): '$line'"); } } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { # vfiler header lines } elsif ($line =~ /^\s*$/ or $line =~ /^===== /) { # vfiler header lines } elsif ($line =~ /Read \(kbytes\)\s+Write \(kbytes\)\s+Read Ops\s+Write Ops\s+Other Ops|Read KBytes\s+Write KBytes\s+Read Ops\s+Write Ops\s+Other Ops/) { if ($got_heading == 0) { # The following inconsistency in the UI was allowed after UI review - see burt 103943 #if ($line =~ m/KBytes/ and $line =~ m/\(kbytes\)/) { # # Check first heading for consistent notation # ExitSoftError('-burt',103943,"Inconsistent kilobytes terminology in heading: '$line'"); #} } $got_heading++; } elsif ($line =~ /lun stats:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { if ($path ne '') { if ($line !~ /^\s+$path\s/) { ExitSoftError("Unrecognized output found from ($cmd): '$line'"); } } else { if ($line !~ /^\s+\S+\s*\(.*\)$/) { ExitSoftError("Unrecognized lun path output found from ($cmd): '$line'"); } } } } if ($got_heading == 0) { ExitSoftError("No heading detected from ($cmd)"); } elsif ($got_heading != $stats[$expected_columns] ) { ExitSoftError("Heading count ($got_heading) is not equal to the count of statistics lines ($stats[$expected_columns])"); } } return @stats; } ## @name ::filer_lun_stats_zero ## @summary Zeroes lun statistics for the filer. ## @arg $fc Required: The filer connection object where this function is executed ## @arg $path Optional: The valid qtree root and name for a lun, defaults to all sub filer_lun_stats_zero ($;$) { my $fc = shift; my $path = shift; if (!defined($path)) { $path = ''; } if ($path ne '') { $path = arg_path($path); # enforce lun_path conventions } my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-reset-stats"); push (@api_args, "path", $path) if ($path ne ''); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "lun stats -z $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result ne cmdOkReturn) { ExitSoftError("Unexpected return code '$result' from ($cmd)"); } } ## @name ::filer_lun_subcommand_help ## @summary Verifies help text appears for lun subcommands with subcommands ## @description After verifying expected text, returns the help text. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $subcommand Required: The subcommand that help is to be displayed for ## @arg $subsubcommand Required: The subsubcommand that help is to be displayed for. ## '' can be used to test ability to list all subcommands. ## @arg @expected a list of expected values each passed as a regular expression sub filer_lun_subcommand_help ($$$;@) { my($fc) = shift; my($subcommand) = shift; my($subsubcommand) = shift; my(@expected) = @_; my $need_to_try_admin = 0; # flag to verify help is NOT present with admin priv my $cmd = "lun $subcommand help $subsubcommand"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context my($text,$result); my $conntype = $fc->{conntype}; if (($subcommand =~ /^attribute$|^df$|^geometry$|^hist$|^maxsize$|^rescan$/) ) { ($text,$result) = filer_cli_diag_say($fc,$cmd,{retry=>'ok'}); $need_to_try_admin = 1; # Verify this command is not shown in admin mode } elsif ($subcommand =~ /^config_check$/) { ($text,$result) = filer_cli_advanced_say($fc,$cmd,{retry=>'ok'}); $need_to_try_admin = 1; # Verify this command is not shown in admin mode } elsif ($subcommand =~ /^clone$/ and $subsubcommand =~ /^status$/) { ($text,$result) = filer_cli_advanced_say($fc,$cmd,{retry=>'ok'}); $need_to_try_admin = 1; # Verify this command is not shown in admin mode } elsif ($subcommand =~ /serial|setup/ && $conntype =~ /rsh/) { # These require advanced if connection is rsh ($text,$result) = filer_cli_advanced_say($fc,$cmd,{retry=>'ok'}); $need_to_try_admin = 1; # Verify this command is not shown in admin mode } else { # All other subcommands are valid from priv set admin ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); } if ($result eq cmdDefaultReturn) { # Unfortunately, these commands still return the default return code } my $foundproto = 0; my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /vdisk/i) { ExitSoftError("A reference to 'vdisk' was found in ($cmd) output: $line"); } if ($line =~ /iniator/i) { ExitSoftError('-burt',75722,"A misspelling 'iniator' was found in ($cmd) output: $line"); } if ($line =~ /nospacereserve/i) { ExitSoftError('-burt',80398,"An old keyword 'nospacereserve' was found in ($cmd) output: $line"); } if ($line =~ / unrecognized command/) { ExitSoftError("Subcommand '$subsubcommand' is not recognized by '$cmd'"); $foundproto = 2; # This skips extra error for missing prototype if ($subcommand =~ /serial|setup/) { burt_found(68909); } } elsif ($line =~ /lun $subcommand $subsubcommand/) { $foundproto = 1; # Exact format of success message is not defined } } if ($foundproto eq 0) { ExitSoftError("No prototype statement found for '$cmd'"); } my @notseen = (); foreach my $expected (@expected) { if (0 == grep { /$expected/ } @info) { if ($expected =~ m/lun_path/ and 0 < grep { /[ <\[]lunpath/ } @info) { ExitSoftError('-burt',132624,"The argument 'lun_path' is preferred over lunpath for consistency"); } else { push(@notseen,$expected); } } } if (@notseen >0 and $foundproto eq 1) { my $not_seen = join("\n",@notseen); ExitSoftError("Missing expected help text output: '$not_seen'"); } # Now verify that special commands not available in admin mode are not shown # in a help request made in that mode if ($need_to_try_admin) { my ($admin_text,$result) = filer_cli_admin_say($fc,$cmd,{retry=>'ok'}); if ($admin_text !~ / unrecognized command/) { if ($admin_text =~ / not found/) { # 105780 A consistent message is needed for unrecognized 'lun' subcommands ExitSoftError('-burt',105780,"Invalid commands should be reported as 'unrecognized command' not 'not found': $cmd"); } else { ExitSoftError("This command should not have help from the admin priviledge: $cmd"); } } } return $text; } ## @name ::filer_lun_unmap ## @summary Disables target mode access to the LUN ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN ## @arg $igroup Required: The initiator group we're unmapping from ## @see-also ::filer_lun_map ## @see-also ::filer_lun_map_low sub filer_lun_unmap ($$$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my($igroup) = shift; my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-unmap", "path", $path, "initiator-group", $igroup); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI $cmd = "lun unmap $path $igroup"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /lun unmap\s?:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined } } } } ## @name ::filer_lun_vaporize ## @summary Destroys a LUN if it exists ## @description Issues a fatal test error only if the lun exists and cannot be destroyed. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $path Required: The valid qtree root and name for this LUN sub filer_lun_vaporize ($$) { my($fc) = shift; my $path = arg_path(shift); # enforce lun_path conventions my $msg = ''; my($cmd,$text,$result); print("LUN to be vaporized: $path\n"); #DEBUG if (filer_lun_exists($fc,$path)) { if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { # Remove any enclosing quotes that may have been added for the CLI $path =~ s/^['"]//; $path =~ s/['"]$//; my @api_args = ("lun-destroy", "path", $path, "force",'true'); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); } else { # Use ONTAP CLI my $cmdname = 'lun destroy'; $cmd = "$cmdname -f $path"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($text =~ /\[wafl_lopri:warning\]: |\[vdisk_admin:notice\]:/) { ErrorSoftError('-burt',67376,"Extraneous messages seen with ($cmd)"); } if ($text =~ m/$cmdname:/) { if ($text =~ m/ No such LUN exists/) { $msg = "LUN disappeared before it could be deleted: $path"; } else { $msg = "Unexpected message from ($cmd)"; } } } if ($result ne cmdOkReturn and $result ne VDISK_ERROR_NO_SUCH_VDISK) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { $msg = "Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"; } } if ($msg ne '') { if (filer_lun_exists($fc,$path)) { # LUN still exists, test must end ExitError($msg); } else { # LUN is gone, do not fail this test logcomment($msg); } } } else { print("LUN to be vaporized, does not exist: $path\n"); } } ## @name ::filer_luns_get ## @summary Returns an array of lun paths on the specified filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_luns_get ($) { my($fc) = shift; my @luns = (); my($cmd,$text,$result); if ($main::FILER_TEST_UI =~ m/ZAPI/i and '' eq filer_vfiler_context_simulate($fc)) { my @api_args = ("lun-list-info"); $cmd = "ZAPI - ".join(', ',@api_args); ($text, $result) = &ZapiUtil::zapi_say($fc->hostp(),@api_args); # $text will be in ZAPI output format $text =~ s/^//; while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag1 = $1; $text =~ s/^<(.*?)>//; if ($tag1 eq 'luns') { while ($text ne '') { if ($text =~ m/^<(.*?)>/) { my $tag2 = $1; $text =~ s/^<(.*?)>//; if ($tag2 eq "\/$tag1") { last; } elsif ($tag2 eq 'path') { if ($text =~ m/(.*?)<\/$tag2>/) { push(@luns,$1); $text =~ s/.*?<\/$tag2>//; } else { ExitError("Missing tag from ZAPI lun-list-info: $text"); } } else { # Skip over this data element $text =~ s/.*?<\/$tag2>//; } } else { ExitSoftError("Unexpected $tag1 content from ZAPI lun-list-info: $text"); } } } elsif($tag1 eq '/results') { last; } else { ExitSoftError("Unexpected level 1 tag: $tag1 from ZAPI lun-list-info: $text"); } } else { ExitSoftError("Unexpected content from ZAPI lun-list-info: $text"); } } } else { # Use ONTAP CLI $cmd = "lun show -v"; $cmd = filer_vfiler_command($fc,$cmd); # Convert command if simulating a vfiler context ($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my $lun = ""; my %HashOfHashes; my @info = filer_cli_split($text); foreach my $line (@info) { if (($line =~ /lun show\s?:/) || ($line =~ /usage:/)) { ExitSoftError("Unexpected message from ($cmd): $line"); } elsif ($line =~ /\/vol\//) { $lun = $line; $lun =~ s/\s+(\/vol\/\S+)\s+\S+\s+\(\d+\).*/$1/; push(@luns,$lun); } else { # accept any extra output } } } return @luns; } ## @name ::filer_nodename_igroups_get ## @summary Returns an array of initiator groups containing the specified nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: An initiator nodename sub filer_nodename_igroups_get ($$) { my($fc) = shift; my($nodename) = arg_nodename(shift); # enforce valid nodename arguments my @igroups = (); my ($key, $value); my $group = ""; # Get output for all igroups my($text) = filer_igroup_show($fc); if ($main::FILER_TEST_UI =~ m/ZAPI/i) { # put into array to be compatible w/ CLI output my @info = split(/>(.*?)<\/.*?/ ) { $key = $1; $value = $2; logcomment("key: $key, value: $value"); if($key eq 'initiator-group-name') { $group = $value; } elsif ($key eq 'initiator-name') { # check for FCP or iSCSI initiator nodename if($value =~ /^iqn\.\S+/ or $value =~ /^eui\.\S+/) { if (lc($nodename) eq lc($value)) { if ($group ne '') { push(@igroups,$group); $group = ''; } } } else { if ($nodename eq $value) { if ($group ne '') { push(@igroups,$group); $group = ''; } } else { $value =~ s/://g; if ($nodename eq $value) { if ($group ne '') { push(@igroups,$group); } $group = ''; } } } } } } } else { # Use ONTAP CLI my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /ostype/) { $group = $line; $group =~ s/\s+(\S+) \(\w+\) \(\w+: \w+\):/$1/; } elsif ($line =~ /^\s+(..:..:..:..:..:..:..:..)\s/) { # An FCP nodename has been found my $found_nodename = $1; if ($nodename eq $found_nodename) { if ($group ne '') { push(@igroups,$group); $group = ''; } } else { $found_nodename =~ s/://g; if ($nodename eq $found_nodename) { if ($group ne '') { push(@igroups,$group); } $group = ''; } } } elsif ($line =~ /^\s+(iqn\.\S+)/ or $line =~ /^\s+(eui\.\S+)/) { # An iSCSI nodename has been found my $found_nodename = $1; if (lc($nodename) eq lc($found_nodename)) { if ($group ne '') { push(@igroups,$group); $group = ''; } } } } } return @igroups; } ## @name ::filer_nodename_lunid_lun_get ## @summary Returns the lun_path for the specified lun id at the specified nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: An initiator nodename ## @arg $lunid Required: A LUN id sub filer_nodename_lunid_lun_get ($$$) { my($fc) = shift; my($nodename) = arg_nodename(shift); # enforce valid nodename arguments my($lunid) = arg_lunid(shift); # enforce valid LUN id arguments my $lun_path; # Get list of all igroups with the specified nodename my @igroups = filer_nodename_igroups_get($fc,$nodename); if (@igroups) { foreach my $igroup (@igroups) { # Now collect the lun_path that matches the id from all igroups with this node name $lun_path = filer_igroup_lunid_lun_get($fc,$igroup,$lunid); if (defined($lun_path)) { last; } } } else { print("No igroups include node name: $nodename\n"); } return $lun_path; } ## @name ::filer_nodename_lunid_vaporize ## @summary Returns the lun_path for the specified lun id at the specified nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: An initiator nodename ## @arg $lunid Required: A LUN id sub filer_nodename_lunid_vaporize ($$$) { my($fc) = shift; my($nodename) = arg_nodename(shift); # enforce valid nodename arguments my($lunid) = arg_lunid(shift); # enforce valid LUN id arguments # Get list of all igroups with the specified nodename my @igroups = filer_nodename_igroups_get($fc,$nodename); if (@igroups) { foreach my $igroup (@igroups) { # Now collect the lun_path that matches the id from all igroups with this node name my $lun_path = filer_igroup_lunid_lun_get($fc,$igroup,$lunid); if (defined($lun_path)) { print "Found LUN: $lun_path in igroup: $igroup at lunid: $lunid\n"; #DEBUG filer_lun_unmap($fc,$lun_path,$igroup); filer_lun_vaporize($fc,$lun_path); last; } print "Found no LUN in igroup: $igroup at lunid: $lunid\n"; #DEBUG } } else { print("No igroups include node name: $nodename\n"); } } ## @name ::filer_nodename_lunids_get ## @summary Returns an array of lun ids that are mapped to the specified nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: An initiator nodename sub filer_nodename_lunids_get ($$) { my($fc) = shift; my($nodename) = arg_nodename(shift); # enforce valid nodename arguments my @lunids = (); # Get output for all igroups my @igroups = filer_nodename_igroups_get($fc,$nodename); if (@igroups) { foreach my $igroup (@igroups) { # Now collect all lunids for all igroups with this node name my @igroup_lunids = filer_igroup_lunids_get($fc,$igroup); push(@lunids,@igroup_lunids); } } else { print("No igroups include initiator: $nodename\n"); } return @lunids; } ## @name ::filer_nodename_luns_get ## @summary Returns an array of lun ids that are mapped to the specified nodename ## @arg $fc Required: The filer connection object for the command line interface ## @arg $nodename Required: An initiator nodename sub filer_nodename_luns_get ($$) { my($fc) = shift; my($nodename) = arg_nodename(shift); # enforce valid nodename arguments my @luns = (); # Get output for all igroups my @igroups = filer_nodename_igroups_get($fc,$nodename); foreach my $igroup (@igroups) { # Now collect all luns for all igroups with this node name my @igroup_luns = filer_igroup_luns_get($fc,$igroup); push(@luns,@igroup_luns); } return @luns; } ## @name ::filer_vld_is_licensable ## @summary Determines if vld can be licensed on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_vld_is_licensable ($) { my($fc) = shift; return filer_service_is_licensable ($fc,'vld'); } ## @name ::filer_vld_is_licensed ## @summary Determines if vld is licensed on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_vld_is_licensed ($) { my($fc) = shift; return filer_service_is_licensed ($fc,'vld'); } ## @name ::filer_vld_license ## @summary Licenses vld on the filer ## @arg $fc Required: The filer connection object for the command line interface ## @arg $days Optional number of days license duration. Default is permanent license sub filer_vld_license ($;$) { my($fc) = shift; my $days = shift; if (!defined($days) ) { $days = ''; } filer_license ($fc,'vld','',$days); } ## @name ::filer_vld_unlicense ## @summary Unlicenses vld service on the filer ## @arg $fc Required: The filer connection object for the command line interface sub filer_vld_unlicense ($) { my($fc) = shift; filer_service_unlicense ($fc,'vld'); } ## @name ::filer_vol_snap_luns_vaporize ## @summary Forces delete of all LUNs backed by the specified snapshot name. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $vol Required: A valid volume with the specified snapshot ## @arg $snapname Required: The snapshot name that contains Backed By sources sub filer_vol_snap_luns_vaporize ($$$) { my($fc) = shift; my $vol = arg_vol_remove_slashvol(shift); my($snapname) = arg_snap_name(shift); my $cmdname = 'lun snap usage'; my $cmd = "$cmdname $vol $snapname"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } elsif ($text =~ m/No such snapshot/) { return; # We are done, no LUNs can exist if the snapshot is gone } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } my $what_to_vaporize = ''; my @luns = (); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /$cmdname:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } else { if ($line =~ m/^\s+LUN:\s/) { if ($what_to_vaporize eq 'lun') { my $next_lun = $line; $next_lun =~ s/^\s+LUN:\s(\S+).*/$1/; filer_lun_vaporize($fc,$next_lun); } } elsif ($line =~ m/^Active:/) { # Destroy Active LUNs until we see snapshot names $what_to_vaporize = 'lun'; } elsif ($line =~ m/^\S+:/) { # Now delete each snapshot $what_to_vaporize = 'snap'; my $snapshot = $line; $snapshot =~ s/^(\S+):.*/$1/; filer_vol_snap_delete($fc,$vol,$snapshot); } } } } ## @name ::iscsi_nodename_new ## @summary Creates one or more random iSCSI nodenames ## @arg $numNodeNames Optional: The number of nodenames requested, default is 1 ## @arg $prefix Optional: The iSCSI nodename prefix, default is 'iqn.1992-08.com.netapp' sub iscsi_nodename_new (;$$) { my $numNodeNames = shift; my @nodeNames = (); my $prefix = shift; if (!defined($prefix) ) { $prefix ='iqn.1992-08.com.netapp'; } $numNodeNames = 1 unless defined $numNodeNames; for(my $n = 0; $n < $numNodeNames; $n++) { my @parts = (); my ($random, $number); push (@parts,$n); for(my $x = 0; $x < 2; $x++) { $random = int(rand(999)) +1; push(@parts, $random); } my $nodename = "$prefix.". join(".", @parts); push(@nodeNames, $nodename); } if (wantarray) { return @nodeNames; } else { return $nodeNames[0]; } } ## @name ::filer_portset_destroy ## @summary Destroys an portset ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Required: Name of the portset sub filer_portset_destroy ($$) { my($fc) = shift; my($portset) = shift; my $cmd = "portset destroy $portset"; my($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ m/portset destroy:/) { ExitError("Unexpected error from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_portset_destroy_force ## @summary Destroys a portset even if bound to an igroup ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Required: Name of the portset sub filer_portset_destroy_force ($$) { my($fc) = shift; my($portset) = shift; my $cmd = "portset destroy -f $portset"; my($text,$result) = filer_cli_say($fc,$cmd); if ($text =~ m/portset destroy:/) { ExitError("Unexpected error from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_portset_is_defined ## @summary Determines if the specified portset is defined on the filer ## @description ## Determines if an portset exists using the 'portset show' command. ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Required: The portset name sub filer_portset_is_defined ($$) { my($fc) = shift; my($portset) = arg_igroup(shift); # enforce valid igroup name arguments my $cmd = "portset show $portset"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /FCP/) { return 1; } } return 0; } ## @name ::filer_portset_is_fcp ## @summary Returns 1 if the portset is for FCP use ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Required: Name of the portset sub filer_portset_is_fcp ($$) { my($fc) = shift; my($portset) = shift; my $cmd = "portset show $portset"; my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /\s\(FCP\)\s/) { return 1; } elsif ($line =~ /\s\(iSCSI\)\s/) { return 0; } } } ## @name ::filer_portset_port_add ## @name ::filer_portset_port_add ## @summary Adds a port to a portset ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Required: Name of the portset ## @arg $port Port name of the port sub filer_portset_port_add ($$$) { my($fc) = shift; my($portset) = shift; my($port) = shift; my $cmd = "portset add $portset $port"; my($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /portset add\s?:/) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined } } if ($text =~ m/portset add:/) { ExitError("Unexpected error from ($cmd)"); } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_portset_port_remove ## @summary Removes a port from a portset ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Required: Name of the portset ## @arg $port Required: Port name of a port in the portset sub filer_portset_port_remove ($$$) { my($fc) = shift; my($portset) = shift; my($port) = shift; ## Burt 65979 should identify expected message text my $cmd = "portset remove $portset $port"; my($text,$result) = filer_cli_say($fc,$cmd); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /portset remove\s?:/) { ExitError("Unexpected message from ($cmd): $line"); } else { # Exact format of success message is not defined } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } } ## @name ::filer_portset_show ## @summary Returns text string of output from the 'portset show' command for the specified portset ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Name of the portset, default shows all sub filer_portset_show ($;$) { my($fc) = shift; my($portset) = shift; my $cmd; if (defined($portset)) { $cmd = "portset show $portset"; } else { $cmd = "portset show"; } my($text,$result) = filer_cli_say($fc,$cmd,{retry=>'ok'}); my @info = filer_cli_split($text); foreach my $line (@info) { if ($line =~ /portset show:/) { ExitSoftError("Unexpected message from ($cmd): $line"); } } if ($result ne cmdOkReturn) { if ($result =~ m/result not found/) { # Assume without an error message, that this is OK } else { ExitSoftError("Unexpected return code ($result), expected (".cmdOkReturn.") from ($cmd)"); } } return $text; } ## @name ::filer_portset_show_get ## @summary Gets a hash of all the info given in portset show ## @arg $fc Required: The filer connection object for the command line interface ## @arg $portset Name of the portset, default shows all sub filer_portset_show_get ($;$) { my($fc) = shift; my $portset = shift; my $text; if (!defined($portset)) { $text = filer_portset_show($fc); } else { $text = filer_portset_show($fc,$portset); } my %HashOfHashes; my $group = ""; my $igroup = ""; my $portname = ""; my $found_filera_ports = 0; my $found_filerb_ports = 0; my $found_igroups = 0; my @info = filer_cli_split($text); my $portset_name = ""; foreach my $line (@info) { if ($line =~ /portset show\s?:/) { # These messages should have been reported in filer_portset_show } elsif ($line =~ /FCP/) { ($portset_name, my $proto) = ($line =~ /\s+(\w+) \((\w+)\):/); $HashOfHashes{$portset_name}{'Portset Name'} = $portset_name; $HashOfHashes{$portset_name}{'Protocol'} = $proto; $found_filera_ports = 0; $found_filerb_ports = 0; $found_igroups = 0; } elsif ($line =~ /ports/) { $found_filera_ports = 1; $found_filerb_ports = 0; } elsif ($line =~ /igroups/) { $found_filerb_ports = 0; $found_igroups = 1; } elsif ($found_filera_ports) { $line =~ s/^\s+//; $portname = $line; $HashOfHashes{$portset_name}{'Portnames_A'} = $portname; $found_filera_ports = 0; $found_filerb_ports = 1; } elsif ($found_filerb_ports) { $line =~ s/^\s+//; $portname = $line; $HashOfHashes{$portset_name}{'Portnames_B'} = $portname; $found_filerb_ports = 0; } elsif ($found_igroups) { $found_igroups = 0; $found_filerb_ports = 0; $line =~ s/^\s+//; $igroup = $line; $HashOfHashes{$portset_name}{'Igroup'} = $igroup; } else { #Get next line } } return %HashOfHashes; } 1; #print "\n ".__FILE__." LOADED \n";