Skip to content

Commit 6c87476

Browse files
committed
fix: create lazy OpenShift context to avoid check timeouts (#865)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent f8dd215 commit 6c87476

18 files changed

+506
-95
lines changed

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/AllContexts.kt

+8-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ package com.redhat.devtools.intellij.kubernetes.model
1313
import com.intellij.openapi.application.ApplicationManager
1414
import com.intellij.openapi.diagnostic.logger
1515
import com.redhat.devtools.intellij.common.utils.ConfigWatcher
16-
import com.redhat.devtools.intellij.common.utils.ExecHelper
1716
import com.redhat.devtools.intellij.kubernetes.model.client.ClientAdapter
1817
import com.redhat.devtools.intellij.kubernetes.model.client.ClientConfig
1918
import com.redhat.devtools.intellij.kubernetes.model.context.Context
@@ -77,7 +76,7 @@ interface IAllContexts {
7776

7877
open class AllContexts(
7978
private val contextFactory: (ClientAdapter<out KubernetesClient>, IResourceModelObservable) -> IActiveContext<out HasMetadata, out KubernetesClient>? =
80-
IActiveContext.Factory::create,
79+
IActiveContext.Factory::createClusterAware,
8180
private val modelChange: IResourceModelObservable,
8281
private val clientFactory: (
8382
namespace: String?,
@@ -200,14 +199,13 @@ open class AllContexts(
200199
return emptyList()
201200
}
202201
lock.read {
203-
return config.allContexts
204-
.map {
205-
if (config.isCurrent(it)) {
206-
createActiveContext(client) ?: Context(it)
207-
} else {
208-
Context(it)
209-
}
202+
return config.allContexts.map {
203+
if (config.isCurrent(it)) {
204+
createActiveContext(client) ?: Context(it)
205+
} else {
206+
Context(it)
210207
}
208+
}
211209
}
212210
}
213211

@@ -223,7 +221,7 @@ open class AllContexts(
223221
}
224222

225223
protected open fun reportTelemetry(context: IActiveContext<out HasMetadata, out KubernetesClient>) {
226-
ExecHelper.submit {
224+
runAsync {
227225
val telemetry = TelemetryService.instance.action(NAME_PREFIX_CONTEXT + "use")
228226
.property(PROP_IS_OPENSHIFT, context.isOpenShift().toString())
229227
try {

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/ResourceModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ open class ResourceModel : IResourceModel {
8383
}
8484

8585
protected open val allContexts: IAllContexts by lazy {
86-
AllContexts(IActiveContext.Factory::create, modelChange)
86+
AllContexts(IActiveContext.Factory::createClusterAware, modelChange)
8787
}
8888

8989
override fun setCurrentContext(context: IContext) {

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/client/ClientAdapter.kt

+35-7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ open class OSClientAdapter(client: OpenShiftClient, private val kubeClient: Kube
3737
ClientConfig(kubeClient.configuration)
3838
}
3939

40+
override fun toOpenShift(): OSClientAdapter {
41+
return this
42+
}
43+
4044
override fun isOpenShift(): Boolean {
4145
return true
4246
}
@@ -52,17 +56,33 @@ open class KubeClientAdapter(client: KubernetesClient) :
5256
override fun isOpenShift(): Boolean {
5357
return false
5458
}
59+
60+
override fun toOpenShift(): OSClientAdapter {
61+
val kubeClient = get()
62+
val osClient = kubeClient.adapt(NamespacedOpenShiftClient::class.java)
63+
return OSClientAdapter(osClient, kubeClient)
64+
}
5565
}
5666

5767
abstract class ClientAdapter<C : KubernetesClient>(private val fabric8Client: C) {
5868

5969
companion object Factory {
6070

71+
const val TIMEOUT_CONNECTION = 5000
72+
const val TIMEOUT_REQUEST = 5000
73+
const val LIMIT_RECONNECT = 2
74+
6175
fun create(
6276
namespace: String? = null,
6377
context: String? = null,
6478
clientBuilder: KubernetesClientBuilder? = null,
65-
createConfig: (context: String?) -> Config = { context -> Config.autoConfigure(context) },
79+
createConfig: (context: String?) -> Config = { context ->
80+
val config = Config.autoConfigure(context)
81+
config.connectionTimeout = TIMEOUT_CONNECTION
82+
config.requestTimeout = TIMEOUT_REQUEST
83+
config.watchReconnectLimit = LIMIT_RECONNECT
84+
config
85+
},
6686
externalTrustManagerProvider: ((toIntegrate: List<X509ExtendedTrustManager>) -> X509TrustManager)? = null
6787
): ClientAdapter<out KubernetesClient> {
6888
KubeConfigEnvValue.copyToSystem()
@@ -76,12 +96,14 @@ abstract class ClientAdapter<C : KubernetesClient>(private val fabric8Client: C)
7696
setSslContext(httpClientBuilder, config, trustManager)
7797
}
7898
.build()
79-
return if (ClusterHelper.isOpenShift(kubeClient)) {
80-
val osClient = kubeClient.adapt(NamespacedOpenShiftClient::class.java)
81-
OSClientAdapter(osClient, kubeClient)
82-
} else {
83-
KubeClientAdapter(kubeClient)
84-
}
99+
/**
100+
* Always create kubernetes client.
101+
* Upgrade existing client to OpenShift only async bcs checking if cluster is OpenShift is costly
102+
* and may timeout if cluster is not reachable.
103+
* @see [issue 865](https://github.com/redhat-developer/intellij-kubernetes/issues/865)
104+
* @see ClientAdapter.toOpenShift
105+
**/
106+
return KubeClientAdapter(kubeClient)
85107
}
86108

87109
private fun setSslContext(
@@ -114,6 +136,8 @@ abstract class ClientAdapter<C : KubernetesClient>(private val fabric8Client: C)
114136
ClientConfig(fabric8Client.configuration)
115137
}
116138

139+
abstract fun toOpenShift(): OSClientAdapter
140+
117141
abstract fun isOpenShift(): Boolean
118142

119143
fun get(): C {
@@ -145,6 +169,10 @@ abstract class ClientAdapter<C : KubernetesClient>(private val fabric8Client: C)
145169
}
146170
}
147171

172+
fun canAdaptToOpenShift(): Boolean {
173+
return ClusterHelper.isOpenShift(fabric8Client)
174+
}
175+
148176
open fun close() {
149177
clients.values.forEach{ it.close() }
150178
fabric8Client.close()

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/ActiveContext.kt

+13-10
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ import java.net.URL
5252

5353
abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
5454
context: NamedContext,
55-
private val modelChange: IResourceModelObservable,
55+
protected val modelChange: IResourceModelObservable,
5656
val client: ClientAdapter<out C>,
57-
protected open val dashboard: IDashboard,
57+
protected open val dashboard: IDashboard? = null,
5858
private var singleResourceOperator: NonCachingSingleResourceOperator = NonCachingSingleResourceOperator(client),
5959
) : Context(context), IActiveContext<N, C> {
6060

@@ -72,8 +72,6 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
7272
ClusterHelper.getClusterInfo(client.get())
7373
}
7474

75-
protected abstract val namespaceKind : ResourceKind<N>
76-
7775
private val extensionName: ExtensionPointName<IResourceOperatorFactory<HasMetadata, KubernetesClient, IResourceOperator<HasMetadata>>> =
7876
ExtensionPointName("com.redhat.devtools.intellij.kubernetes.resourceOperators")
7977

@@ -307,7 +305,7 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
307305
override fun stopWatch(kind: ResourceKind<out HasMetadata>) {
308306
logger<ActiveContext<*, *>>().debug("Stop watching $kind resources.")
309307
watch.stopWatch(kind)
310-
// don't notify invalidation change because this would cause UI to reload
308+
// don't notify invalidation change because this would cause the UI to reload
311309
// and therefore to repopulate the cache immediately.
312310
// Any resource operation that eventually happens while the watch is not active would cause the cache
313311
// to become out-of-sync and it would therefore return invalid resources when asked to do so
@@ -489,27 +487,32 @@ abstract class ActiveContext<N : HasMetadata, C : KubernetesClient>(
489487
override fun close() {
490488
logger<ActiveContext<*, *>>().debug("Closing context $name.")
491489
watch.close()
492-
dashboard.close()
490+
dashboard?.close()
493491
}
494492

495493
private fun <P: IResourceOperator<out HasMetadata>> getAllResourceOperators(type: Class<P>)
496494
: MutableMap<ResourceKind<out HasMetadata>, P> {
497495
val operators = mutableMapOf<ResourceKind<out HasMetadata>, P>()
498496
operators.putAll(
499-
getInternalResourceOperators(client)
497+
getInternalResourceOperators()
500498
.filterIsInstance(type)
501499
.associateBy { it.kind })
502500
operators.putAll(
503-
getExtensionResourceOperators(client)
501+
getExtensionResourceOperators()
504502
.filterIsInstance(type)
505503
.associateBy { it.kind })
506504
return operators
507505
}
508506

509-
protected abstract fun getInternalResourceOperators(client: ClientAdapter<out C>): List<IResourceOperator<out HasMetadata>>
507+
abstract override fun getInternalResourceOperators(): List<IResourceOperator<out HasMetadata>>
510508

511-
protected open fun getExtensionResourceOperators(client: ClientAdapter<out C>): List<IResourceOperator<out HasMetadata>> {
509+
protected open fun getExtensionResourceOperators(): List<IResourceOperator<out HasMetadata>> {
512510
return extensionName.extensionList
513511
.map { it.create(client.get()) }
514512
}
513+
514+
override fun getDashboardUrl(): String? {
515+
return dashboard?.get()
516+
}
517+
515518
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2020 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.kubernetes.model.context
12+
13+
import com.redhat.devtools.intellij.kubernetes.model.IResourceModelObservable
14+
import com.redhat.devtools.intellij.kubernetes.model.client.KubeClientAdapter
15+
import com.redhat.devtools.intellij.kubernetes.model.client.OSClientAdapter
16+
import com.redhat.devtools.intellij.kubernetes.model.resource.IResourceOperator
17+
import com.redhat.devtools.intellij.kubernetes.model.resource.ResourceKind
18+
import com.redhat.devtools.intellij.kubernetes.model.resource.kubernetes.KubernetesReplicas.Replicator
19+
import io.fabric8.kubernetes.api.model.HasMetadata
20+
import io.fabric8.kubernetes.api.model.NamedContext
21+
import io.fabric8.kubernetes.client.KubernetesClient
22+
import org.jetbrains.concurrency.runAsync
23+
import java.util.concurrent.locks.ReentrantReadWriteLock
24+
import kotlin.concurrent.read
25+
import kotlin.concurrent.write
26+
27+
class ClusterAwareContext(
28+
context: NamedContext,
29+
modelChange: IResourceModelObservable,
30+
client: KubeClientAdapter,
31+
/* for testing purposes */
32+
private val kubernetesContextFactory: (
33+
context: NamedContext,
34+
modelChange: IResourceModelObservable,
35+
client: KubeClientAdapter,
36+
) -> IActiveContext<out HasMetadata, out KubernetesClient>
37+
= ::KubernetesContext,
38+
/* for testing purposes */
39+
private val openshiftContextFactory: (
40+
client: OSClientAdapter,
41+
modelChange: IResourceModelObservable
42+
) -> IActiveContext<out HasMetadata, out KubernetesClient>?
43+
= IActiveContext.Factory::createOpenShift,
44+
/* for testing purposes */
45+
private val runAsync: (runnable: () -> Unit) -> Unit
46+
= ::runAsync
47+
) : KubernetesContext(context, modelChange, client) {
48+
49+
private val lock = ReentrantReadWriteLock()
50+
private var delegate: IActiveContext<out HasMetadata, out KubernetesClient> =
51+
kubernetesContextFactory.invoke(context, modelChange, client)
52+
53+
init {
54+
runAsync.invoke {
55+
createOpenShiftDelegate()
56+
}
57+
}
58+
59+
override val namespaceKind: ResourceKind<out HasMetadata>
60+
get() = delegate.namespaceKind
61+
62+
override fun getInternalResourceOperators(): List<IResourceOperator<out HasMetadata>> {
63+
lock.read {
64+
return delegate.getInternalResourceOperators()
65+
}
66+
}
67+
68+
override fun isOpenShift(): Boolean {
69+
lock.read {
70+
return delegate.isOpenShift()
71+
}
72+
}
73+
74+
override fun setReplicas(replicas: Int, replicator: Replicator) {
75+
lock.read {
76+
delegate.setReplicas(replicas, replicator)
77+
}
78+
}
79+
80+
override fun getReplicas(resource: HasMetadata): Replicator? {
81+
lock.read {
82+
return delegate.getReplicas(resource)
83+
}
84+
}
85+
86+
override fun getDashboardUrl(): String? {
87+
lock.read {
88+
return delegate.getDashboardUrl()
89+
}
90+
}
91+
92+
private fun createOpenShiftDelegate() {
93+
if (client.canAdaptToOpenShift()) {
94+
val delegate = openshiftContextFactory.invoke(
95+
client.toOpenShift(),
96+
modelChange
97+
) ?: return
98+
lock.write {
99+
this.delegate = delegate
100+
}
101+
modelChange.fireModified(this)
102+
}
103+
}
104+
}
105+

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/Context.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface IContext {
1919
val namespace: String?
2020
}
2121

22-
open class Context(private val context: NamedContext): IContext {
22+
open class Context(protected val context: NamedContext): IContext {
2323
override val active: Boolean = false
2424
override val name: String?
2525
get() = context.name

0 commit comments

Comments
 (0)