Skip to content

Commit 283decb

Browse files
committed
Deterministic resource naming + keep track of SP instances
1 parent a87aad4 commit 283decb

15 files changed

Lines changed: 223 additions & 51 deletions

src/main/kotlin/eu/openanalytics/shinyproxyoperator/components/ConfigMapFactory.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import mu.KotlinLogging
1313
class ConfigMapFactory(private val kubeClient: KubernetesClient) {
1414

1515
private val logger = KotlinLogging.logger {}
16-
private val mapper = ObjectMapper(YAMLFactory())
1716

1817
fun create(shinyProxy: ShinyProxy): ConfigMap {
1918
val configMapDefinition: ConfigMap = ConfigMapBuilder()
2019
.withNewMetadata()
21-
.withGenerateName(shinyProxy.metadata.name.toString() + "-configmap-")
22-
.withLabels(mapOf(ShinyProxyController.APP_LABEL to shinyProxy.metadata.name))
20+
.withName(ResourceNameFactory.createNameForConfigMap(shinyProxy))
21+
.withLabels(LabelFactory.labelsForCurrentShinyProxyInstance(shinyProxy))
2322
.addNewOwnerReference()
2423
.withController(true)
2524
.withKind("ShinyProxy")
@@ -28,7 +27,7 @@ class ConfigMapFactory(private val kubeClient: KubernetesClient) {
2827
.withNewUid(shinyProxy.metadata.uid)
2928
.endOwnerReference()
3029
.endMetadata()
31-
.addToData("application-in.yml", mapper.writeValueAsString(shinyProxy.spec))
30+
.addToData("application-in.yml", shinyProxy.specAsYaml)
3231
.build()
3332

3433
val createdConfigMap = kubeClient.configMaps().inNamespace(shinyProxy.metadata.namespace).create(configMapDefinition)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package eu.openanalytics.shinyproxyoperator.components
2+
3+
import eu.openanalytics.shinyproxyoperator.controller.ShinyProxyController
4+
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxy
5+
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxyInstance
6+
7+
object LabelFactory {
8+
9+
fun labelsForCurrentShinyProxyInstance(shinyProxy: ShinyProxy): Map<String, String> {
10+
return mapOf(
11+
APP_LABEL to APP_LABEL_VALUE,
12+
NAME_LABEL to shinyProxy.metadata.name,
13+
INSTANCE_LABEL to shinyProxy.calculateHashOfCurrentSpec()
14+
)
15+
}
16+
17+
fun labelsForShinyProxyInstance(shinyProxy: ShinyProxy, shinyProxyInstance: ShinyProxyInstance): Map<String, String> {
18+
val hashOfSpec: String = shinyProxyInstance.hashOfSpec.let {
19+
it ?: TODO("Should not happend")
20+
}
21+
return mapOf(
22+
APP_LABEL to APP_LABEL_VALUE,
23+
NAME_LABEL to shinyProxy.metadata.name,
24+
INSTANCE_LABEL to hashOfSpec
25+
)
26+
}
27+
28+
const val APP_LABEL = "app"
29+
const val APP_LABEL_VALUE = "shinyproxy"
30+
const val NAME_LABEL = "sp-name"
31+
const val INSTANCE_LABEL = "sp-instance"
32+
33+
}

src/main/kotlin/eu/openanalytics/shinyproxyoperator/components/ReplicaSetFactory.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ class ReplicaSetFactory(private val kubeClient: KubernetesClient ) {
1818

1919
private val logger = KotlinLogging.logger {}
2020

21-
suspend fun create(shinyProxy: ShinyProxy,configMap: ConfigMap): ReplicaSet {
21+
suspend fun create(shinyProxy: ShinyProxy): ReplicaSet {
2222
val replicaSetDefinition: ReplicaSet = ReplicaSetBuilder()
2323
.withNewMetadata()
24-
.withGenerateName(shinyProxy.metadata.name.toString() + "-replicaset-")
25-
.withNamespace(shinyProxy.metadata.namespace)
26-
.withLabels(mapOf(ShinyProxyController.APP_LABEL to shinyProxy.metadata.name))
27-
.addNewOwnerReference()
24+
.withName(ResourceNameFactory.createNameForReplicaSet(shinyProxy))
25+
.withNamespace(shinyProxy.metadata.namespace)
26+
.withLabels(LabelFactory.labelsForCurrentShinyProxyInstance(shinyProxy))
27+
.addNewOwnerReference()
2828
.withController(true)
2929
.withKind("ShinyProxy")
3030
.withApiVersion("openanalytics.eu/v1alpha1")
@@ -35,13 +35,13 @@ class ReplicaSetFactory(private val kubeClient: KubernetesClient ) {
3535
.withNewSpec()
3636
.withReplicas(1)
3737
.withNewSelector()
38-
.withMatchLabels(mapOf(ShinyProxyController.APP_LABEL to shinyProxy.metadata.name))
38+
.withMatchLabels(LabelFactory.labelsForCurrentShinyProxyInstance(shinyProxy))
3939
.endSelector()
4040
.withNewTemplate()
4141
.withNewMetadata()
42-
.withGenerateName(shinyProxy.metadata.name.toString() + "-pod-")
42+
.withGenerateName(ResourceNameFactory.createNameForPod(shinyProxy))
4343
.withNamespace(shinyProxy.metadata.namespace)
44-
.withLabels(Collections.singletonMap(ShinyProxyController.APP_LABEL, shinyProxy.metadata.name))
44+
.withLabels(LabelFactory.labelsForCurrentShinyProxyInstance(shinyProxy))
4545
.endMetadata()
4646
.withNewSpec()
4747
.addNewContainer()
@@ -56,7 +56,7 @@ class ReplicaSetFactory(private val kubeClient: KubernetesClient ) {
5656
.withVolumes(VolumeBuilder()
5757
.withName("config-volume")
5858
.withConfigMap(ConfigMapVolumeSourceBuilder()
59-
.withName(configMap.metadata.name)
59+
.withName(ResourceNameFactory.createNameForConfigMap(shinyProxy))
6060
.build())
6161
.build())
6262
.endSpec()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package eu.openanalytics.shinyproxyoperator.components
2+
3+
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxy
4+
import mu.KotlinLogging
5+
6+
object ResourceNameFactory {
7+
8+
private val logger = KotlinLogging.logger {}
9+
10+
fun createNameForService(shinyProxy: ShinyProxy): String {
11+
return "sp-${shinyProxy.metadata.name}-svc-${shinyProxy.calculateHashOfCurrentSpec()}".substring(0 until 63)
12+
}
13+
14+
fun createNameForConfigMap(shinyProxy: ShinyProxy): String {
15+
return "sp-${shinyProxy.metadata.name}-cm-${shinyProxy.calculateHashOfCurrentSpec()}".substring(0 until 63)
16+
}
17+
18+
fun createNameForPod(shinyProxy: ShinyProxy): String {
19+
return "sp-${shinyProxy.metadata.name}-pod-${shinyProxy.calculateHashOfCurrentSpec()}".substring(0 until 63)
20+
}
21+
22+
fun createNameForReplicaSet(shinyProxy: ShinyProxy): String {
23+
return "sp-${shinyProxy.metadata.name}-rs-${shinyProxy.calculateHashOfCurrentSpec()}".substring(0 until 63)
24+
}
25+
26+
}

src/main/kotlin/eu/openanalytics/shinyproxyoperator/components/ServiceFactory.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ class ServiceFactory(private val kubeClient: KubernetesClient) {
1515
suspend fun create(shinyProxy: ShinyProxy): Service? {
1616
val serviceDefinition: Service = ServiceBuilder()
1717
.withNewMetadata()
18-
.withGenerateName(shinyProxy.metadata.name.toString() + "-service-")
18+
.withName(ResourceNameFactory.createNameForService(shinyProxy))
1919
.withNamespace(shinyProxy.metadata.namespace)
20-
.withLabels(mapOf(ShinyProxyController.APP_LABEL to shinyProxy.metadata.name))
20+
.withLabels(LabelFactory.labelsForCurrentShinyProxyInstance(shinyProxy))
2121
.addNewOwnerReference()
2222
.withController(true)
2323
.withKind("ShinyProxy")
@@ -32,7 +32,7 @@ class ServiceFactory(private val kubeClient: KubernetesClient) {
3232
.withPort(80)
3333
.withTargetPort(IntOrString(8080))
3434
.endPort()
35-
.withSelector(mapOf(ShinyProxyController.APP_LABEL to shinyProxy.metadata.name))
35+
.withSelector(LabelFactory.labelsForCurrentShinyProxyInstance(shinyProxy))
3636
.endSpec()
3737
.build()
3838

src/main/kotlin/eu/openanalytics/shinyproxyoperator/controller/ResourceListener.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package eu.openanalytics.shinyproxyoperator.controller
22

3+
import eu.openanalytics.shinyproxyoperator.components.LabelFactory
34
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxy
45
import io.fabric8.kubernetes.api.model.HasMetadata
56
import io.fabric8.kubernetes.api.model.OwnerReference
@@ -40,8 +41,12 @@ class ResourceListener<T : HasMetadata>(private val channel: SendChannel<ShinyPr
4041
if (ownerReference.kind.toLowerCase() != "shinyproxy") {
4142
return
4243
}
44+
4345
val shinyProxy = shinyProxyLister[ownerReference.name] ?: return
44-
channel.send(ShinyProxyEvent(ShinyProxyEventType.UPDATE_DEPENDENCY, shinyProxy))
46+
val hashOfInstance = resource.metadata.labels[LabelFactory.INSTANCE_LABEL] ?: TODO("Should not happen")
47+
val shinyProxyInstance = shinyProxy.status.getInstanceByHash(hashOfInstance) ?: TODO("Should not happen")
48+
49+
channel.send(ShinyProxyEvent(ShinyProxyEventType.UPDATE_DEPENDENCY, shinyProxy, shinyProxyInstance))
4550
}
4651

4752

@@ -52,6 +57,7 @@ class ResourceListener<T : HasMetadata>(private val channel: SendChannel<ShinyPr
5257
return ownerReference
5358
}
5459
}
60+
5561
return null
5662
}
5763

src/main/kotlin/eu/openanalytics/shinyproxyoperator/controller/ResourceRetriever.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ class ResourceRetriever(private val replicaSetLister: Lister<ReplicaSet>,
2525
return configMaps
2626
}
2727

28+
fun getConfigMapByLabels(labels: Map<String, String>): List<ConfigMap> {
29+
val configMaps = arrayListOf<ConfigMap>()
30+
for (configmap in configMapLister.list()) {
31+
if (configmap?.metadata?.labels?.entries?.containsAll(labels.entries) == true) {
32+
configMaps.add(configmap)
33+
logger.debug { "Found ConfigMap ${configmap.metadata.name}" }
34+
}
35+
}
36+
logger.info { "ConfigMapCount: ${configMaps.size}, ${configMaps.map { it.metadata.name }}" }
37+
return configMaps
38+
}
39+
2840
fun getReplicaSetByLabel(label: String, shinyProxyName: String): ArrayList<ReplicaSet> {
2941
val replicaSets = arrayListOf<ReplicaSet>()
3042
for (replicaSet in replicaSetLister.list()) {
@@ -37,6 +49,18 @@ class ResourceRetriever(private val replicaSetLister: Lister<ReplicaSet>,
3749
return replicaSets
3850
}
3951

52+
fun getReplicaSetByLabels(labels: Map<String, String>): ArrayList<ReplicaSet> {
53+
val replicaSets = arrayListOf<ReplicaSet>()
54+
for (replicaSet in replicaSetLister.list()) {
55+
if (replicaSet?.metadata?.labels?.entries?.containsAll(labels.entries) == true) {
56+
replicaSets.add(replicaSet)
57+
logger.debug { "Found ReplicaSet ${replicaSet.metadata.name} phase => ${replicaSet.status}" }
58+
}
59+
}
60+
logger.info { "ReplicaSetCount: ${replicaSets.size}, ${replicaSets.map { it.metadata.name }}" }
61+
return replicaSets
62+
}
63+
4064
fun getServiceByLabel(label: String, shinyProxyName: String): List<Service> {
4165
val services = arrayListOf<Service>()
4266
for (service in serviceLister.list()) {
@@ -49,4 +73,16 @@ class ResourceRetriever(private val replicaSetLister: Lister<ReplicaSet>,
4973
return services
5074
}
5175

76+
fun getServiceByLabels(labels: Map<String, String>): List<Service> {
77+
val services = arrayListOf<Service>()
78+
for (service in serviceLister.list()) {
79+
if (service?.metadata?.labels?.entries?.containsAll(labels.entries) == true) {
80+
services.add(service)
81+
logger.debug { "Found ReplicaSet ${service.metadata.name} phase => ${service.status}" }
82+
}
83+
}
84+
logger.info { "ServiceCount: ${services.size}, ${services.map { it.metadata.name }}" }
85+
return services
86+
}
87+
5288
}
Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
package eu.openanalytics.shinyproxyoperator.controller
22

33
import eu.openanalytics.shinyproxyoperator.components.ConfigMapFactory
4+
import eu.openanalytics.shinyproxyoperator.components.LabelFactory
45
import eu.openanalytics.shinyproxyoperator.components.ReplicaSetFactory
56
import eu.openanalytics.shinyproxyoperator.components.ServiceFactory
7+
import eu.openanalytics.shinyproxyoperator.crd.DoneableShinyProxy
68
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxy
9+
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxyInstance
10+
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxyList
711
import io.fabric8.kubernetes.api.model.ConfigMap
812
import io.fabric8.kubernetes.api.model.Service
913
import io.fabric8.kubernetes.api.model.apps.ReplicaSet
1014
import io.fabric8.kubernetes.client.KubernetesClient
15+
import io.fabric8.kubernetes.client.dsl.MixedOperation
16+
import io.fabric8.kubernetes.client.dsl.Resource
1117
import io.fabric8.kubernetes.client.informers.SharedIndexInformer
1218
import io.fabric8.kubernetes.client.informers.cache.Lister
1319
import kotlinx.coroutines.channels.Channel
1420
import mu.KotlinLogging
1521

1622

1723
class ShinyProxyController(private val kubernetesClient: KubernetesClient,
24+
private val shinyProxyClient: MixedOperation<ShinyProxy, ShinyProxyList, DoneableShinyProxy, Resource<ShinyProxy, DoneableShinyProxy>>,
1825
private val replicaSetInformer: SharedIndexInformer<ReplicaSet>,
1926
serviceInformer: SharedIndexInformer<Service>,
2027
configMapInformer: SharedIndexInformer<ConfigMap>,
2128
private val shinyProxyInformer: SharedIndexInformer<ShinyProxy>,
2229
namespace: String) {
2330

24-
private val workqueue = Channel<ShinyProxyEvent>()
31+
private val workqueue = Channel<ShinyProxyEvent>(10000)
2532
private val shinyProxyLister = Lister(shinyProxyInformer.indexer, namespace)
2633
private val replicaSetLister = Lister(replicaSetInformer.indexer, namespace)
2734
private val configMapLister = Lister(configMapInformer.indexer, namespace)
@@ -48,16 +55,18 @@ class ShinyProxyController(private val kubernetesClient: KubernetesClient,
4855

4956
when (event.eventType) {
5057
ShinyProxyEventType.ADD -> {
51-
reconcileSingleShinyProxy(event.shinyProxy)
58+
reconcileSingleShinyProxy(event.shinyProxy, event.shinyProxyInstance)
5259
}
5360
ShinyProxyEventType.UPDATE -> {
5461
// TODO calculate hash -> reconcile
62+
reconcileSingleShinyProxy(event.shinyProxy, event.shinyProxyInstance)
5563
}
5664
ShinyProxyEventType.DELETE -> {
57-
deleteSingleShinyProxy(event.shinyProxy)
65+
// DELETE is not needed
66+
// deleteSingleShinyProxy(event.shinyProxy)
5867
}
5968
ShinyProxyEventType.UPDATE_DEPENDENCY -> {
60-
reconcileSingleShinyProxy(event.shinyProxy)
69+
reconcileSingleShinyProxy(event.shinyProxy, event.shinyProxyInstance)
6170
}
6271
}
6372

@@ -68,36 +77,53 @@ class ShinyProxyController(private val kubernetesClient: KubernetesClient,
6877
}
6978
}
7079

71-
private fun deleteSingleShinyProxy(shinyProxy: ShinyProxy) {
72-
logger.info { "DeleteSingleShinyProxy: ${shinyProxy.metadata.name}" }
73-
for (service in resourceRetriever.getServiceByLabel(APP_LABEL, shinyProxy.metadata.name)) {
74-
kubernetesClient.resource(service).delete()
80+
// private fun deleteSingleShinyProxy(shinyProxy: ShinyProxy) {
81+
// logger.info { "DeleteSingleShinyProxy: ${shinyProxy.metadata.name}" }
82+
// for (service in resourceRetriever.getServiceByLabel(APP_LABEL, shinyProxy.metadata.name)) {
83+
// kubernetesClient.resource(service).delete()
84+
// }
85+
// for (replicaSet in resourceRetriever.getReplicaSetByLabel(APP_LABEL, shinyProxy.metadata.name)) {
86+
// kubernetesClient.resource(replicaSet).delete()
87+
// }
88+
// for (configMap in resourceRetriever.getConfigMapByLabel(APP_LABEL, shinyProxy.metadata.name)) {
89+
// kubernetesClient.resource(configMap).delete()
90+
// }
91+
// }
92+
93+
private suspend fun reconcileSingleShinyProxy(shinyProxy: ShinyProxy, shinyProxyInstance: ShinyProxyInstance?) {
94+
logger.info { "ReconcileSingleShinyProxy: ${shinyProxy.metadata.name}" }
95+
96+
if (shinyProxy.status.instances.isEmpty()) {
97+
val instance = ShinyProxyInstance()
98+
instance.hashOfSpec = shinyProxy.calculateHashOfCurrentSpec()
99+
instance.isLatestInstance = true
100+
shinyProxy.status.instances.add(instance)
101+
shinyProxyClient.updateStatus(shinyProxy)
102+
return
75103
}
76-
for (replicaSet in resourceRetriever.getReplicaSetByLabel(APP_LABEL, shinyProxy.metadata.name)) {
77-
kubernetesClient.resource(replicaSet).delete()
104+
105+
if (shinyProxyInstance == null) {
106+
TODO("Should not happend")
78107
}
79-
for (configMap in resourceRetriever.getConfigMapByLabel(APP_LABEL, shinyProxy.metadata.name)) {
80-
kubernetesClient.resource(configMap).delete()
108+
109+
if (shinyProxyInstance.hashOfSpec == null) {
110+
TODO("Should not happend")
81111
}
82-
}
83112

84-
private suspend fun reconcileSingleShinyProxy(shinyProxy: ShinyProxy) {
85-
logger.info { "ReconcileSingleShinyProxy: ${shinyProxy.metadata.name}" }
86-
val configMaps = resourceRetriever.getConfigMapByLabel(APP_LABEL, shinyProxy.metadata.name)
113+
val configMaps = resourceRetriever.getConfigMapByLabels(LabelFactory.labelsForShinyProxyInstance(shinyProxy, shinyProxyInstance))
87114
if (configMaps.isEmpty()) {
88115
logger.debug { "0 ConfigMaps found -> creating ConfigmMap" }
89116
configMapFactory.create(shinyProxy)
90117
return
91118
}
92119

93-
val replicaSets = resourceRetriever.getReplicaSetByLabel(APP_LABEL, shinyProxy.metadata.name)
120+
val replicaSets = resourceRetriever.getReplicaSetByLabels(LabelFactory.labelsForShinyProxyInstance(shinyProxy, shinyProxyInstance))
94121
if (replicaSets.isEmpty()) {
95122
logger.debug { "0 ReplicaSets found -> creating ReplicaSet" }
96-
val configMap = configMaps[0]
97-
replicaSetFactory.create(shinyProxy, configMap)
123+
replicaSetFactory.create(shinyProxy)
98124
return
99125
}
100-
val services = resourceRetriever.getServiceByLabel(APP_LABEL, shinyProxy.metadata.name)
126+
val services = resourceRetriever.getServiceByLabels(LabelFactory.labelsForShinyProxyInstance(shinyProxy, shinyProxyInstance))
101127
if (services.isEmpty()) {
102128
logger.debug { "0 Services found -> creating Service" }
103129
serviceFactory.create(shinyProxy)
@@ -106,8 +132,4 @@ class ShinyProxyController(private val kubernetesClient: KubernetesClient,
106132
}
107133

108134

109-
companion object {
110-
const val APP_LABEL = "app"
111-
}
112-
113135
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package eu.openanalytics.shinyproxyoperator.controller
22

33
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxy
4+
import eu.openanalytics.shinyproxyoperator.crd.ShinyProxyInstance
45

5-
data class ShinyProxyEvent(val eventType: ShinyProxyEventType, val shinyProxy: ShinyProxy)
6+
data class ShinyProxyEvent(val eventType: ShinyProxyEventType, val shinyProxy: ShinyProxy, val shinyProxyInstance: ShinyProxyInstance?)

0 commit comments

Comments
 (0)