+Install_Feature_On_List_Or_All
+ [Arguments] ${feature_name} ${member_index_list}=${EMPTY} ${timeout}=60s
+ [Documentation] Attempt installation on each member from list (or all). Then look for failures.
+ ${index_list} = ClusterManagement__Given_Or_Internal_Index_List given_list=${member_index_list}
+ ${status_list} = BuiltIn.Create_List
+ : FOR ${index} IN @{index_list}
+ \ ${status} ${text} = BuiltIn.Run_Keyword_And_Ignore_Error Install_Feature_On_Member feature_name=${feature_name} member_index=${index}
+ \ ... timeout=${timeout}
+ \ BuiltIn.Log ${text}
+ \ Collections.Append_To_List ${status_list} ${status}
+ : FOR ${status} IN @{status_list}
+ \ BuiltIn.Run_Keyword_If "${status}" != "PASS" BuiltIn.Fail ${feature_name} installation failed, see log.
+
+Install_Feature_On_Member
+ [Arguments] ${feature_name} ${member_index} ${timeout}=60s
+ [Documentation] Run feature:install karaf command, fail if installation was not successful. Return output.
+ ${status} ${output} = BuiltIn.Run_Keyword_And_Ignore_Error Run_Karaf_Command_On_Member command=feature:install ${feature_name} member_index=${member_index} timeout=${timeout}
+ BuiltIn.Run_Keyword_If "${status}" != "PASS" BuiltIn.Fail Failed to install ${feature_name}: ${output}
+ BuiltIn.Should_Not_Contain ${output} Can't install Failed to install ${feature_name}: ${output}
+ [Return] ${output}
+
+With_Ssh_To_List_Or_All_Run_Keyword
+ [Arguments] ${member_index_list} ${keyword_name} @{args} &{kwargs}
+ [Documentation] For each index in given list (or all): activate SSH connection, run given Keyword, close active connection. Return None.
+ ... Note that if the Keyword affects SSH connections, results are still deterministic, but perhaps undesirable.
+ ... Beware that in order to avoid "got positional argument after named arguments", first two arguments in the call should not be named.
+ BuiltIn.Comment This keyword is experimental and there is high risk of being replaced by another approach.
+ # TODO: For_Index_From_List_Or_All_Run_Keyword applied to With_Ssh_To_Member_Run_Keyword?
+ ${index_list} = ClusterManagement__Given_Or_Internal_Index_List given_list=${member_index_list}
+ : FOR ${member_index} IN @{index_list}
+ \ ${member_ip} = Resolve_IP_Address_For_Member ${member_index}
+ \ SSHKeywords.Open_Connection_To_Odl_System ip_address=${member_ip}
+ \ BuiltIn.Run_Keyword ${keyword_name} @{args} &{kwargs}
+ \ SSHLibrary.Close_Connection
+
+Safe_With_Ssh_To_List_Or_All_Run_Keyword
+ [Arguments] ${member_index_list} ${keyword_name} @{args} &{kwargs}
+ [Documentation] Remember active ssh connection index, call With_Ssh_To_List_Or_All_Run_Keyword, return None. Restore the conection index on teardown.
+ SSHKeywords.Run_Keyword_Preserve_Connection With_Ssh_To_List_Or_All_Run_Keyword ${member_index_list} ${keyword_name} @{args} &{kwargs}
+
+Clean_Directories_On_List_Or_All
+ [Arguments] ${member_index_list}=${EMPTY} ${directory_list}=${EMPTY} ${karaf_home}=${KARAF_HOME}
+ [Documentation] Clear @{directory_list} or @{ODL_DEFAULT_DATA_PATHS} for members in given list or all. Return None.
+ ... This is intended to return Karaf (offline) to the state it was upon the first boot.
+ ${path_list} = Builtin.Set Variable If "${directory_list}" == "${EMPTY}" ${ODL_DEFAULT_DATA_PATHS} ${directory_list}
+ Safe_With_Ssh_To_List_Or_All_Run_Keyword ${member_index_list} ClusterManagement__Clean_Directories ${path_list} ${karaf_home}
+
+ClusterManagement__Clean_Directories
+ [Arguments] ${relative_path_list} ${karaf_home}
+ [Documentation] For each relative path, remove files with respect to ${karaf_home}. Return None.
+ : FOR ${relative_path} IN @{relative_path_list}
+ \ SSHLibrary.Execute_Command rm -rf ${karaf_home}${/}${relative_path}
+