Skip to content

Commit

Permalink
GH-226 Enhancement: Check connection for config file in parallel
Browse files Browse the repository at this point in the history
Signed-off-by: Katsiaryna Tsytsenia <[email protected]>
  • Loading branch information
Katsiaryna Tsytsenia authored and ktsytsen committed Jan 31, 2025
1 parent 4646b27 commit abc6037
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 61 deletions.
166 changes: 105 additions & 61 deletions src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ 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 +184,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 +243,97 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
}

/**
* Checks the zowe connection using InfoOperation. It is also needed to load zos version and
* Checks the zowe connections using InfoOperation. 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 ...)
* @param zoweConnections list of connection to check and prepare.
* @param type of zowe config
* @throws ProcessCanceledException if connection has been canceled by user
*/
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 goodConnections = 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"
service<DataOpsManager>().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 =
service<DataOpsManager>().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
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
}
goodConnections.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 goodConnections to failedConnections
}

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

val (allPreparedConn, failedConnections) = testAndPrepareAllZosmfConnections(zoweConfig, type)
val (goodConnections, failedConnections) = testAndPrepareConnections(
zoweConfig.getListOfZosmfConections(),
type
)
if (checkConnection and failedConnections.isNotEmpty()) {
val andMore = if (failedConnections.size > 3) "..." else ""
notifyUiOnConnectionFailure(
Expand All @@ -300,9 +362,9 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService
)
}
val conToAdd = if (checkConnection)
allPreparedConn.subtract(failedConnections.toSet())
goodConnections
else
allPreparedConn
goodConnections + failedConnections
conToAdd.forEach { zosmfConnection ->
val connectionOpt = configCrudable.addOrUpdate(zosmfConnection)
if (!connectionOpt.isEmpty) {
Expand All @@ -315,7 +377,14 @@ 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 */
}
}
}

Expand All @@ -338,31 +407,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 +442,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 +465,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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
Expand Down Expand Up @@ -316,6 +317,82 @@ class ZoweConfigServiceTestSpec : WithApplicationShouldSpec({
assertSoftly { addOrUpdateCalledCount shouldBe 1 }
}

should("cancel testing Zowe config connections") {
val testFailProfileName5 = "test_profile_name_fail5"
var extractSecurePropertiesCalledCount = 0
var cancelationCount = 0

dataOpsManagerServiceMock.testInstance = object : TestDataOpsManagerImpl() {
override fun <R : Any> performOperation(operation: Operation<R>, progressIndicator: ProgressIndicator): R {
return when (operation) {
is InfoOperation -> {
infoOperationCount += 1
println("!!!")
println((operation as InfoOperation).connectionConfig.uuid)
if ((operation as InfoOperation).connectionConfig.uuid==("throw")){
cancelationCount += 1
throw ProcessCanceledException()
}
else {
mockk<SystemsResponse>() as R
}
}
else -> {
mockk<Any>() as R
}
}
}
}

val globalZoweConfig: ZoweConfig = mockk {
every {
extractSecureProperties(any<Array<String>>(), any<KeytarWrapper>())
} answers {
extractSecurePropertiesCalledCount += 1
}
every {
getListOfZosmfConections()
} returns listOf(
mockk {
every { user } returns "TSTUSR"
every { password } returns "TSTPWD"
every { profileName } returns testFailProfileName5
every { basePath } returns "test/base/path/"
every { host } returns "testFailHost5"
every { zosmfPort } returns "1234"
every { protocol } returns "https"
every { rejectUnauthorized } returns null
}
)
}

every { parseConfigJsonRef(any<InputStream>()) } returns globalZoweConfig

every {
configServiceCrudableMock.find(any<Class<out ConnectionConfig>>(), any<Predicate<in ConnectionConfig>>())
} answers {
listOf<ConnectionConfig>(
mockk {
every { uuid } returns "throw"
every { zVersion } returns ZVersion.ZOS_2_4
every { name } returns "$ZOWE_PROJECT_PREFIX${ZoweConfigType.GLOBAL}-$testFailProfileName5"
every { zoweConfigPath } returns System.getProperty("user.home").replace("((\\*)|(/*))$", "") + "/.zowe/" + ZOWE_CONFIG_NAME
}
)
.filter(secondArg<Predicate<ConnectionConfig>>()::test)
.stream()
}

val zoweConfigService = ZoweConfigServiceImpl(projectMock)

zoweConfigService
.addOrUpdateZoweConfig(scanProject = true, checkConnection = true, ZoweConfigType.GLOBAL)
assertSoftly { cancelationCount shouldBe 1 }
assertSoftly { setCredentialsCalledCount shouldBe 1 }
assertSoftly { infoOperationCount shouldBe 1 }

}

should("add a new connection for the local Zowe config, scanning a project, with failed connections and their check") {
val testSuccessProfileName = "test_profile_name_success"
val testFailProfileName1 = "test_profile_name_fail1"
Expand Down Expand Up @@ -543,6 +620,7 @@ class ZoweConfigServiceTestSpec : WithApplicationShouldSpec({
val testFailProfileName2 = "test_profile_name_fail2"
val testFailProfileName3 = "test_profile_name_fail3"
val testFailProfileName4 = "test_profile_name_fail4"
val testFailProfileName5 = "test_profile_name_fail5"
val testFailHost1 = "test1.com"
val testFailHost2 = "test2.com"
val testFailHost3 = "test3.com"
Expand Down

0 comments on commit abc6037

Please sign in to comment.