Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-226 Enhancement: Check connection for config file in parallel #214

Open
wants to merge 1 commit into
base: release/v2.2.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 168 additions & 65 deletions src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VirtualFileManager
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.zowe.explorer.config.ConfigService
import org.zowe.explorer.config.connect.ConnectionConfig
import org.zowe.explorer.config.connect.CredentialService
Expand Down Expand Up @@ -180,7 +183,7 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
}
}
} catch (e: Exception) {
NotificationsService.errorNotification(e, project = myProject, custTitle="Error with Zowe config file")
NotificationsService.errorNotification(e, project = myProject, custTitle = "Error with Zowe config file")
return null
}
}
Expand Down Expand Up @@ -239,42 +242,99 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
}

/**
* Checks the zowe connection using InfoOperation. It is also needed to load zos version and
* Checks all passed zowe connections using InfoOperation in parallel. It is also needed to load zos version and
* real owner of the connection (acceptable for alias users).
* IMPORTANT!!! It modifies passed connection object by setting zVersion and owner.
* @param zoweConnection connection to check and prepare.
* @throws Throwable if something went wrong (connection was not established or one of requests was failed ...)
* IMPORTANT!!! It modifies passed connection objects by setting zVersion and owner.
* @param zoweConnections list of connection to check and prepare.
* @param type of zowe config
* @throws ProcessCanceledException if connection has been canceled by user
* @return a pair of lists with successfully tested connections and invalid connections
*/
private fun testAndPrepareConnection(zoweConnection: ConnectionConfig) {
val throwable = runTask("Testing Connection to ${zoweConnection.url}", myProject) { indicator ->
return@runTask try {
runCatching {
DataOpsManager.getService().performOperation(InfoOperation(zoweConnection), indicator)
}.onSuccess {
indicator.text = "Retrieving z/OS information"
val systemInfo = DataOpsManager.getService().performOperation(ZOSInfoOperation(zoweConnection), indicator)
zoweConnection.zVersion = when (systemInfo.zosVersion) {
"04.25.00" -> ZVersion.ZOS_2_2
"04.26.00" -> ZVersion.ZOS_2_3
"04.27.00" -> ZVersion.ZOS_2_4
"04.28.00" -> ZVersion.ZOS_2_5
"04.29.00" -> ZVersion.ZOS_3_1
else -> ZVersion.ZOS_2_1
@Throws(ProcessCanceledException::class)
private fun testAndPrepareConnections(
zoweConnections: List<ZOSConnection>,
type: ZoweConfigType
): Pair<MutableList<ConnectionConfig>, MutableList<ConnectionConfig>> {
val succeededConnections = mutableListOf<ConnectionConfig>()
val failedConnections = mutableListOf<ConnectionConfig>()
val results = mutableListOf<Deferred<ConnectionConfig>>()
var throwable: ProcessCanceledException? = null
runTask("Testing Connections...", myProject, cancellable = true) { indicator ->
runBlocking {
for (zosmfConnection in zoweConnections) {
results.add(async {
val zoweConnection: ConnectionConfig = prepareConnection(zosmfConnection, type)
try {
runCatching {
if (!indicator.isCanceled) {
indicator.text = if (zoweConnections.size == 1)
"Testing Connection to ${zoweConnection.url}"
else
"Testing Connection to ${zoweConnections.size} connections"
DataOpsManager.getService().performOperation(InfoOperation(zoweConnection), indicator)
} else {
throw ProcessCanceledException()
}
}.onSuccess {
if (!indicator.isCanceled) {

indicator.text = if (zoweConnections.size == 1)
"Retrieving z/OS information for ${zoweConnection.url}"
else
"Retrieving z/OS information for ${zoweConnections.size} connections"
val systemInfo =
DataOpsManager.getService().performOperation(
ZOSInfoOperation(zoweConnection),
indicator
)
zoweConnection.zVersion = when (systemInfo.zosVersion) {
"04.25.00" -> ZVersion.ZOS_2_2
"04.26.00" -> ZVersion.ZOS_2_3
"04.27.00" -> ZVersion.ZOS_2_4
"04.28.00" -> ZVersion.ZOS_2_5
"04.29.00" -> ZVersion.ZOS_3_1
else -> ZVersion.ZOS_2_1
}
} else {
throw ProcessCanceledException()
}
}.onSuccess {
if (!indicator.isCanceled) {
indicator.text = if (zoweConnections.size == 1)
"Retrieving user information for ${zoweConnection.url}"
else
"Retrieving user information for ${zoweConnections.size} connections"
zoweConnection.owner = whoAmI(zoweConnection) ?: ""
} else {
throw ProcessCanceledException()
}
}.onFailure {
if (indicator.isCanceled)
throw ProcessCanceledException()
else {
throw it
}
}
} catch (t: Throwable) {
if (t is ProcessCanceledException)
throwable = t
failedConnections.add(zoweConnection)
return@async zoweConnection
}
succeededConnections.add(zoweConnection)
return@async zoweConnection
}
}.onSuccess {
indicator.text = "Retrieving user information"
zoweConnection.owner = whoAmI(zoweConnection) ?: ""
}.onFailure {
throw it
)
}
null
} catch (t: Throwable) {
t
}
}
if (throwable != null) {
throw throwable
throw ProcessCanceledException()
}
runBlocking {
results.map { it.await() }
}
return succeededConnections to failedConnections
}

/**
Expand All @@ -290,19 +350,44 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
this.globalZoweConfig
zoweConfig ?: throw Exception("Cannot get $type Zowe config")

val (allPreparedConn, failedConnections) = testAndPrepareAllZosmfConnections(zoweConfig, type)
val allConnectionsToTest = zoweConfig.getListOfZosmfConections()
val uniqueConnectionsToTest = allConnectionsToTest.filterIndexed { _, element ->
allConnectionsToTest.filter {
it.host == element.host &&
it.zosmfPort == element.zosmfPort &&
it.user == element.user &&
it.password == element.password &&
it.protocol == element.protocol &&
it.rejectUnauthorized == element.rejectUnauthorized &&
it.basePath == element.basePath &&
it.encoding == element.encoding &&
it.responseTimeout == element.responseTimeout
}.sortedWith(compareBy { it.profileName.length })[0] == element
}
val duplicatedConnections = (allConnectionsToTest + uniqueConnectionsToTest).groupBy { it.profileName }
.filter { it.value.size == 1 }
.flatMap { it.value }

val (succeededConnections, failedConnections) =
testAndPrepareConnections(uniqueConnectionsToTest, type)

for (tmpZOSMFConn in duplicatedConnections) {
restoreFullConnectionList(tmpZOSMFConn, succeededConnections, type)
restoreFullConnectionList(tmpZOSMFConn, failedConnections, type)
}

if (checkConnection and failedConnections.isNotEmpty()) {
val andMore = if (failedConnections.size > 3) "..." else ""
notifyUiOnConnectionFailure(
"Connection failed to:",
"${failedConnections.joinToString(separator = ", <p>") { it.url }} $andMore",
"Unsuccessfully tested profiles:",
"${failedConnections.joinToString(separator = ", <p>") { getProfileNameFromConnName(it.name) }} $andMore",
type
)
}
val conToAdd = if (checkConnection)
allPreparedConn.subtract(failedConnections.toSet())
succeededConnections
else
allPreparedConn
succeededConnections + failedConnections
conToAdd.forEach { zosmfConnection ->
val connectionOpt = configCrudable.addOrUpdate(zosmfConnection)
if (!connectionOpt.isEmpty) {
Expand All @@ -315,7 +400,49 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
}

} catch (e: Exception) {
NotificationsService.errorNotification(e, project = myProject, custTitle="Error with Zowe config file")
if (e !is ProcessCanceledException)
NotificationsService.errorNotification(
e,
project = myProject,
custTitle = "Error with Zowe config file"
)
else { /* Nothing to do */
}
}
}

/**
* Restore connection lists after testing
* @param tmpZOSMFConn duplicated connection to restore.
* @param connListToRestore list with tested connections (succeeded or failed)
* @param type of zowe config
*/
private fun restoreFullConnectionList(
tmpZOSMFConn: ZOSConnection,
connListToRestore: MutableList<ConnectionConfig>,
type: ZoweConfigType
) {
val tmpConn = tmpZOSMFConn.toConnectionConfig(uuid = UUID.randomUUID().toString(), type = type)
val existingConn = findExistingConnection(type, tmpZOSMFConn.profileName)
if (existingConn?.name != null) {
tmpConn.name = existingConn.name
}
val tmpDuplicatedConnList = connListToRestore.filter {
it.isAllowSelfSigned == tmpConn.isAllowSelfSigned &&
it.url == tmpConn.url &&
it.name != tmpConn.name &&
CredentialService.getUsername(it) == tmpZOSMFConn.user &&
CredentialService.getPassword(it).contentEquals(tmpZOSMFConn.password.toCharArray())
}
if (tmpDuplicatedConnList.isNotEmpty()) {
val tmpDuplConn = tmpDuplicatedConnList[0].clone()
if (existingConn?.name != null){
tmpDuplConn.name = existingConn.name
tmpDuplConn.uuid = existingConn.uuid
}
CredentialService.getService()
.setCredentials(tmpConn.uuid, tmpZOSMFConn.user, tmpZOSMFConn.password.toCharArray())
connListToRestore.add(tmpDuplConn)
}
}

Expand All @@ -338,31 +465,6 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
return zoweConnection
}

/**
* Convert all zosmf connections from zowe config file to ConnectionConfig and tests them
* @param zoweConfig
* @param type of zowe config
* @return pair of lists, one is the connections list, the second is the list of URLs which did not pass the test
*/
private fun testAndPrepareAllZosmfConnections(
zoweConfig: ZoweConfig,
type: ZoweConfigType
): Pair<List<ConnectionConfig>, List<ConnectionConfig>> {
return zoweConfig.getListOfZosmfConections()
.fold(
mutableListOf<ConnectionConfig>() to mutableListOf<ConnectionConfig>()
) { (allConnectionConfigs, failedURLs), zosmfConnection ->
val zoweConnection = prepareConnection(zosmfConnection, type)
try {
testAndPrepareConnection(zoweConnection)
} catch (t: Throwable) {
failedURLs.add(zoweConnection)
}
allConnectionConfigs.add(zoweConnection)
allConnectionConfigs to failedURLs
}
}

/**
* @see ZoweConfigService.deleteZoweConfig
*/
Expand Down Expand Up @@ -398,7 +500,7 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
}

} catch (e: Exception) {
NotificationsService.errorNotification(e, project = myProject, custTitle="Error with Zowe config file")
NotificationsService.errorNotification(e, project = myProject, custTitle = "Error with Zowe config file")
}
}

Expand All @@ -421,13 +523,13 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
var host = "localhost"
var port = "10443"
if (matcher.matches()) {
if (matcher.group(2) != null)
if (!matcher.group(2).isNullOrBlank())
host = matcher.group(2)
if (matcher.group(3) != null)
if (!matcher.group(3).isNullOrBlank())
port = matcher.group(3).substring(1)
}

val content = getResourceAsStreamWrappable(ZoweConfigServiceImpl::class.java.classLoader ,"files/${ZOWE_CONFIG_NAME}")
val content = getResourceAsStreamWrappable(ZoweConfigServiceImpl::class.java.classLoader, "files/${ZOWE_CONFIG_NAME}")
.use { iS -> iS?.readAllBytes()?.let { String(it, charset) } }
?.replace("<PORT>".toRegex(), port)
?.replace("<HOST>".toRegex(), "\"$host\"")
Expand Down Expand Up @@ -521,6 +623,7 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
if (findAllZosmfExistingConnection(type).isEmpty()) return ZoweConfigState.NEED_TO_ADD

val zoweConfigZosmfConnections = zoweConfig.getListOfZosmfConections()

return zoweConfigZosmfConnections
.fold(ZoweConfigState.SYNCHRONIZED) { prevZoweConfigState, zosConnection ->
val existingConnection = findExistingConnection(type, zosConnection.profileName)
Expand Down
Loading