From 2d3ca67f4a9499d9e65adaa3e4499bc341ce2736 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Aug 2017 10:01:38 +0530 Subject: [PATCH 01/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (splited the code) --- .../model/KubernetesServerGroup.groovy | 32 +- .../KubernetesControllersCachingAgent.groovy | 355 ++++++++++++++++++ .../config/KubernetesProviderConfig.groovy | 1 + 3 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy index 46bb841eb5f..b91569e761b 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy @@ -32,6 +32,7 @@ import io.fabric8.kubernetes.api.model.ReplicationController import io.fabric8.kubernetes.api.model.HorizontalPodAutoscaler import io.fabric8.kubernetes.api.model.extensions.ReplicaSet import io.fabric8.kubernetes.client.internal.SerializationUtils +import io.kubernetes.client.models.V1beta1StatefulSet @CompileStatic @EqualsAndHashCode(includes = ["name", "namespace", "account"]) @@ -63,6 +64,10 @@ class KubernetesServerGroup implements ServerGroup, Serializable { Map getBuildInfo() { def imageList = [] def buildInfo = [:] + /** + * I have added a null check as in statefullset deployDescription is null + */ + if (deployDescription != null) { for (def container : this.deployDescription.containers) { imageList.add(KubernetesUtil.getImageIdWithoutRegistry(container.imageDescription)) } @@ -72,7 +77,7 @@ class KubernetesServerGroup implements ServerGroup, Serializable { def parsedName = Names.parseName(name) buildInfo.createdBy = this.deployDescription?.deployment?.enabled ? parsedName.cluster : null - + } return buildInfo } @@ -102,6 +107,31 @@ class KubernetesServerGroup implements ServerGroup, Serializable { this.namespace = namespace } + KubernetesServerGroup(V1beta1StatefulSet statefulSet, String account, List events) { + this.name = statefulSet.metadata?.name + this.account = account + this.region = statefulSet.metadata?.namespace + this.namespace = this.region + this.createdTime = statefulSet.metadata?.creationTimestamp?.getMillis() + this.zones = [this.region] as Set + this.securityGroups = [] + this.replicas = statefulSet.spec?.replicas ?: 0 + // Prashant 3 this.loadBalancers = KubernetesUtil.getLoadBalancers(statefulSet) as Set + this.launchConfig = [:] + this.labels = statefulSet.spec?.template?.metadata?.labels + this.deployDescription = null + //this.yaml = SerializationUtils.dumpWithoutRuntimeStateAsYaml(statefulSet) + this.kind = statefulSet.kind + this.events = events?.collect { + new KubernetesEvent(it) + } + /*if (autoscaler) { + KubernetesApiConverter.attachAutoscaler(this.deployDescription, autoscaler) + this.autoscalerStatus = new KubernetesAutoscalerStatus(autoscaler) + }*/ + //this.revision = KubernetesApiAdaptor.getDeploymentRevision(statefulSet) + } + KubernetesServerGroup(ReplicaSet replicaSet, String account, List events, HorizontalPodAutoscaler autoscaler) { this.name = replicaSet.metadata?.name this.account = account diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy new file mode 100644 index 00000000000..2373e7e83cd --- /dev/null +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy @@ -0,0 +1,355 @@ +/* + * Copyright 2017 Cisco, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.clouddriver.kubernetes.provider.agent + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.frigga.Names +import com.netflix.spinnaker.cats.agent.AgentDataType +import com.netflix.spinnaker.cats.agent.CacheResult +import com.netflix.spinnaker.cats.agent.DefaultCacheResult +import com.netflix.spinnaker.cats.cache.CacheData +import com.netflix.spinnaker.cats.provider.ProviderCache +import com.netflix.spinnaker.clouddriver.cache.OnDemandAgent +import com.netflix.spinnaker.clouddriver.cache.OnDemandMetricsSupport +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.KubernetesUtil +import com.netflix.spinnaker.clouddriver.kubernetes.model.KubernetesServerGroup +import com.netflix.spinnaker.clouddriver.kubernetes.provider.view.MutableCacheData +import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials +import com.netflix.spinnaker.cats.cache.DefaultCacheData +import com.netflix.spinnaker.clouddriver.kubernetes.cache.Keys +import com.netflix.spectator.api.Registry +import groovy.util.logging.Slf4j +import io.fabric8.kubernetes.api.model.Event +import io.fabric8.kubernetes.api.model.HorizontalPodAutoscaler +import io.kubernetes.client.models.V1beta1StatefulSet + +/** + * Created by spinnaker on 20/8/17. + */ +@Slf4j +class KubernetesControllersCachingAgent extends KubernetesCachingAgent implements OnDemandAgent{ + final String category = 'serverGroup' + final OnDemandMetricsSupport metricsSupport + + KubernetesControllersCachingAgent(String accountName, ObjectMapper objectMapper, KubernetesCredentials credentials, int agentIndex, int agentCount, Registry registry) { + super(accountName, objectMapper, credentials, agentIndex, agentCount) + this.metricsSupport = new OnDemandMetricsSupport(registry, + this, + "$kubernetesCloudProvider.id:$OnDemandAgent.OnDemandType.ServerGroup") + } + + @Override + String getOnDemandAgentType() { + return "${getAgentType()}-OnDemand" + } + + @Override + OnDemandMetricsSupport getMetricsSupport() { + return null + } + + @Override + boolean handles(OnDemandAgent.OnDemandType type, String cloudProvider) { + return false + } + + @Override + OnDemandAgent.OnDemandResult handle(ProviderCache providerCache, Map data) { + if (!data.containsKey("serverGroupName")) { + return null + } + + if (data.account != accountName) { + return null + } + + reloadNamespaces() + String namespace = data.region + if (!namespaces.contains(namespace)) { + return null + } + + def serverGroupName = data.serverGroupName.toString() + + + + V1beta1StatefulSet statefulSet = metricsSupport.readData { + loadStatefulSets() + } + + CacheResult result = metricsSupport.transformData { + buildCacheResult([new StateFulSet(statefulSet: statefulSet)], [:], [], Long.MAX_VALUE) + } + def jsonResult = objectMapper.writeValueAsString(result.cacheResults) + + if (result.cacheResults.values().flatten().isEmpty()) { + // Avoid writing an empty onDemand cache record (instead delete any that may have previously existed). + providerCache.evictDeletedItems(Keys.Namespace.ON_DEMAND.ns, [Keys.getServerGroupKey(accountName, namespace, serverGroupName)]) + } else { + metricsSupport.onDemandStore { + def cacheData = new DefaultCacheData( + Keys.getServerGroupKey(accountName, namespace, serverGroupName), + 10 * 60, // ttl is 10 minutes + [ + cacheTime: System.currentTimeMillis(), + cacheResults: jsonResult, + processedCount: 0, + processedTime: null + ], + [:] + ) + + providerCache.putCacheData(Keys.Namespace.ON_DEMAND.ns, cacheData) + } + } + // Evict this server group if it no longer exists. + Map> evictions = statefulSet ? [:] : [ + (Keys.Namespace.SERVER_GROUPS.ns): [ + Keys.getServerGroupKey(accountName, namespace, serverGroupName) + ] + ] + log.info("On demand cache refresh (data: ${data}) succeeded.") + + return new OnDemandAgent.OnDemandResult( + sourceAgentType: getOnDemandAgentType(), + cacheResult: result, + evictions: evictions + ) + } + + @Override + Collection pendingOnDemandRequests(ProviderCache providerCache) { + return null + } + + /** + * @return the data types this Agent returns + * @see com.netflix.spinnaker.cats.agent.AgentDataType.Authority + */ + @Override + Collection getProvidedDataTypes() { + return KubernetesServerGroupCachingAgent.types + } + + /** + * Triggered by an AgentScheduler to tell this Agent to load its data. + * + * @param providerCache Cache associated with this Agent's provider + * @return the complete set of data for this Agent. + */ + @Override + CacheResult loadData(ProviderCache providerCache) { + reloadNamespaces() + Long start = System.currentTimeMillis() + List statefulSet = loadStatefulSets() + List serverGroups = (statefulSet.collect { + it ? new StateFulSet(statefulSet: it) : null + } + ) - null + List evictFromOnDemand = [] + List keepInOnDemand = [] + providerCache.getAll(Keys.Namespace.ON_DEMAND.ns, + serverGroups.collect { serverGroup -> + Keys.getServerGroupKey(accountName, serverGroup.namespace, serverGroup.name) + }) + .each { CacheData onDemandEntry -> + // Ensure that we don't overwrite data that was inserted by the `handle` method while we retrieved the + // replication controllers. Furthermore, cache data that hasn't been processed needs to be updated in the ON_DEMAND + // cache, so don't evict data without a processedCount > 0. + if (onDemandEntry.attributes.cacheTime < start && onDemandEntry.attributes.processedCount > 0) { + evictFromOnDemand << onDemandEntry + } else { + keepInOnDemand << onDemandEntry + } + } + + def result = buildCacheResult(serverGroups, keepInOnDemand.collectEntries { CacheData onDemandEntry -> + [(onDemandEntry.id): onDemandEntry] + }, evictFromOnDemand*.id, start) + + result.cacheResults[Keys.Namespace.ON_DEMAND.ns].each { CacheData onDemandEntry -> + onDemandEntry.attributes.processedTime = System.currentTimeMillis() + onDemandEntry.attributes.processedCount = (onDemandEntry.attributes.processedCount ?: 0) + 1 + } + + return result + } + + @Override + String getSimpleName() { + return KubernetesControllersCachingAgent.simpleName + } + + List loadStatefulSets() { + namespaces.collect { String namespace -> + credentials.apiClientAdaptor.getStatefulSets(namespace) + }.flatten() + } + + private CacheResult buildCacheResult(List serverGroups, Map onDemandKeep, List onDemandEvict, Long start) { + log.info("Describing items in ${agentType}") + + Map cachedApplications = MutableCacheData.mutableCacheMap() + Map cachedClusters = MutableCacheData.mutableCacheMap() + Map cachedServerGroups = MutableCacheData.mutableCacheMap() + Map cachedInstances = MutableCacheData.mutableCacheMap() + Map cachedLoadBalancers = MutableCacheData.mutableCacheMap() + + // Map namespace -> name -> event + Map> rcEvents = [:].withDefault { _ -> [:] } + Map> rsEvents = [:].withDefault { _ -> [:] } + try { + namespaces.each { String namespace -> + rcEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, KubernetesUtil.DEPRECATED_SERVER_GROUP_KIND) + rsEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, KubernetesUtil.SERVER_GROUP_KIND) + } + } catch (Exception e) { + log.warn "Failure fetching events for all server groups in $namespaces", e + } + + // Map namespace -> name -> autoscaler + Map> rcAutoscalers = [:].withDefault { _ -> [:] } + Map> rsAutoscalers = [:].withDefault { _ -> [:] } + Map> deployAutoscalers = [:].withDefault { _ -> [:] } + try { + namespaces.each { String namespace -> + rcAutoscalers[namespace] = credentials.apiAdaptor.getAutoscalers(namespace, KubernetesUtil.DEPRECATED_SERVER_GROUP_KIND) + rsAutoscalers[namespace] = credentials.apiAdaptor.getAutoscalers(namespace, KubernetesUtil.SERVER_GROUP_KIND) + deployAutoscalers[namespace] = credentials.apiAdaptor.getAutoscalers(namespace, KubernetesUtil.DEPLOYMENT_KIND) + } + } catch (Exception e) { + log.warn "Failure fetching autoscalers for all server groups in $namespaces", e + } + + for (StateFulSet serverGroup: serverGroups) { + if (!serverGroup.exists()) { + continue + } + + def onDemandData = onDemandKeep ? onDemandKeep[Keys.getServerGroupKey(accountName, serverGroup.namespace, serverGroup.name)] : null + + if (onDemandData && onDemandData.attributes.cacheTime >= start) { + Map> cacheResults = objectMapper.readValue(onDemandData.attributes.cacheResults as String, + new TypeReference>>() { }) + cache(cacheResults, Keys.Namespace.APPLICATIONS.ns, cachedApplications) + cache(cacheResults, Keys.Namespace.CLUSTERS.ns, cachedClusters) + cache(cacheResults, Keys.Namespace.SERVER_GROUPS.ns, cachedServerGroups) + cache(cacheResults, Keys.Namespace.INSTANCES.ns, cachedInstances) + } else { + def serverGroupName = serverGroup.name + def names = Names.parseName(serverGroupName) + def applicationName = names.app + def clusterName = names.cluster + def serverGroupKey = Keys.getServerGroupKey(accountName, serverGroup.namespace, serverGroupName) + def applicationKey = Keys.getApplicationKey(applicationName) + def clusterKey = Keys.getClusterKey(accountName, applicationName, category, clusterName) + def instanceKeys = [] + def loadBalancerKeys = serverGroup.loadBalancers.collect({ + Keys.getLoadBalancerKey(accountName, serverGroup.namespace, it) + }) + + cachedApplications[applicationKey].with { + attributes.name = applicationName + relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) + relationships[Keys.Namespace.SERVER_GROUPS.ns].add(serverGroupKey) + relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) + } + + cachedClusters[clusterKey].with { + attributes.name = clusterName + relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) + relationships[Keys.Namespace.SERVER_GROUPS.ns].add(serverGroupKey) + relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) + } + + cachedServerGroups[serverGroupKey].with { + def events = null + attributes.name = serverGroupName + + if (serverGroup.statefulSet) { + + events = rsEvents[serverGroup.namespace][serverGroupName] + } + attributes.serverGroup = new KubernetesServerGroup(serverGroup.statefulSet, accountName, events) + relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) + relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) + relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) + relationships[Keys.Namespace.INSTANCES.ns].addAll(instanceKeys) + } + } + } + + log.info("Caching ${cachedApplications.size()} applications in ${agentType}") + log.info("Caching ${cachedClusters.size()} clusters in ${agentType}") + log.info("Caching ${cachedServerGroups.size()} server groups in ${agentType}") + log.info("Caching ${cachedInstances.size()} instances in ${agentType}") + + new DefaultCacheResult([ + (Keys.Namespace.APPLICATIONS.ns): cachedApplications.values(), + (Keys.Namespace.LOAD_BALANCERS.ns): cachedLoadBalancers.values(), + (Keys.Namespace.CLUSTERS.ns): cachedClusters.values(), + (Keys.Namespace.SERVER_GROUPS.ns): cachedServerGroups.values(), + (Keys.Namespace.INSTANCES.ns): cachedInstances.values(), + (Keys.Namespace.ON_DEMAND.ns): onDemandKeep.values() + ],[ + (Keys.Namespace.ON_DEMAND.ns): onDemandEvict, + ]) + + } + + private static void cache(Map> cacheResults, String cacheNamespace, Map cacheDataById) { + cacheResults[cacheNamespace].each { + def existingCacheData = cacheDataById[it.id] + if (existingCacheData) { + existingCacheData.attributes.putAll(it.attributes) + it.relationships.each { String relationshipName, Collection relationships -> + existingCacheData.relationships[relationshipName].addAll(relationships) + } + } else { + cacheDataById[it.id] = it + } + } + } + + class StateFulSet{ + + V1beta1StatefulSet statefulSet + String getName() { + statefulSet.metadata.name + } + + String getNamespace() { + statefulSet.metadata.namespace + } + + Map getSelector() { + statefulSet.spec.selector.matchLabels + } + + boolean exists() { + statefulSet + } + + List getLoadBalancers() { + KubernetesUtil.getLoadBalancers(statefulSet) + } + + + + } +} diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/config/KubernetesProviderConfig.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/config/KubernetesProviderConfig.groovy index e85cca9a41e..f08d3a608e5 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/config/KubernetesProviderConfig.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/config/KubernetesProviderConfig.groovy @@ -100,6 +100,7 @@ class KubernetesProviderConfig implements Runnable { (0.. newlyAddedAgents << new KubernetesLoadBalancerCachingAgent(credentials.name, credentials.credentials, objectMapper, index, credentials.cacheThreads, registry) + newlyAddedAgents << new KubernetesControllersCachingAgent(credentials.name, objectMapper,credentials.credentials, index, credentials.cacheThreads,registry) newlyAddedAgents << new KubernetesSecurityGroupCachingAgent(credentials.name, credentials.credentials, objectMapper, index, credentials.cacheThreads, registry) newlyAddedAgents << new KubernetesServerGroupCachingAgent(credentials.name, credentials.credentials, objectMapper, index, credentials.cacheThreads, registry) newlyAddedAgents << new KubernetesInstanceCachingAgent(credentials.name, credentials.credentials, objectMapper, index, credentials.cacheThreads) From 00dd525c107b7353f4af09378aa0013689dcf8e6 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Aug 2017 10:49:21 +0530 Subject: [PATCH 02/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (Statefulset Part 1) --- .../model/KubernetesServerGroup.groovy | 7 ---- .../KubernetesControllersCachingAgent.groovy | 38 +++---------------- .../view/KubernetesProviderUtils.groovy | 5 +++ 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy index b91569e761b..0825ba2a602 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy @@ -116,20 +116,13 @@ class KubernetesServerGroup implements ServerGroup, Serializable { this.zones = [this.region] as Set this.securityGroups = [] this.replicas = statefulSet.spec?.replicas ?: 0 - // Prashant 3 this.loadBalancers = KubernetesUtil.getLoadBalancers(statefulSet) as Set this.launchConfig = [:] this.labels = statefulSet.spec?.template?.metadata?.labels this.deployDescription = null - //this.yaml = SerializationUtils.dumpWithoutRuntimeStateAsYaml(statefulSet) this.kind = statefulSet.kind this.events = events?.collect { new KubernetesEvent(it) } - /*if (autoscaler) { - KubernetesApiConverter.attachAutoscaler(this.deployDescription, autoscaler) - this.autoscalerStatus = new KubernetesAutoscalerStatus(autoscaler) - }*/ - //this.revision = KubernetesApiAdaptor.getDeploymentRevision(statefulSet) } KubernetesServerGroup(ReplicaSet replicaSet, String account, List events, HorizontalPodAutoscaler autoscaler) { diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy index 2373e7e83cd..13b1b451967 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy @@ -210,30 +210,15 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement Map cachedInstances = MutableCacheData.mutableCacheMap() Map cachedLoadBalancers = MutableCacheData.mutableCacheMap() - // Map namespace -> name -> event - Map> rcEvents = [:].withDefault { _ -> [:] } - Map> rsEvents = [:].withDefault { _ -> [:] } - try { - namespaces.each { String namespace -> - rcEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, KubernetesUtil.DEPRECATED_SERVER_GROUP_KIND) - rsEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, KubernetesUtil.SERVER_GROUP_KIND) - } - } catch (Exception e) { - log.warn "Failure fetching events for all server groups in $namespaces", e - } + Map> stateFulsetEvents = [:].withDefault { _ -> [:] } - // Map namespace -> name -> autoscaler - Map> rcAutoscalers = [:].withDefault { _ -> [:] } - Map> rsAutoscalers = [:].withDefault { _ -> [:] } - Map> deployAutoscalers = [:].withDefault { _ -> [:] } try { namespaces.each { String namespace -> - rcAutoscalers[namespace] = credentials.apiAdaptor.getAutoscalers(namespace, KubernetesUtil.DEPRECATED_SERVER_GROUP_KIND) - rsAutoscalers[namespace] = credentials.apiAdaptor.getAutoscalers(namespace, KubernetesUtil.SERVER_GROUP_KIND) - deployAutoscalers[namespace] = credentials.apiAdaptor.getAutoscalers(namespace, KubernetesUtil.DEPLOYMENT_KIND) + stateFulsetEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, "V1beta1StatefulSet") + } } catch (Exception e) { - log.warn "Failure fetching autoscalers for all server groups in $namespaces", e + log.warn "Failure fetching events for all server groups in $namespaces", e } for (StateFulSet serverGroup: serverGroups) { @@ -259,22 +244,17 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement def applicationKey = Keys.getApplicationKey(applicationName) def clusterKey = Keys.getClusterKey(accountName, applicationName, category, clusterName) def instanceKeys = [] - def loadBalancerKeys = serverGroup.loadBalancers.collect({ - Keys.getLoadBalancerKey(accountName, serverGroup.namespace, it) - }) cachedApplications[applicationKey].with { attributes.name = applicationName relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) relationships[Keys.Namespace.SERVER_GROUPS.ns].add(serverGroupKey) - relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) } cachedClusters[clusterKey].with { attributes.name = clusterName relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) relationships[Keys.Namespace.SERVER_GROUPS.ns].add(serverGroupKey) - relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) } cachedServerGroups[serverGroupKey].with { @@ -283,12 +263,11 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement if (serverGroup.statefulSet) { - events = rsEvents[serverGroup.namespace][serverGroupName] + events = stateFulsetEvents[serverGroup.namespace][serverGroupName] } attributes.serverGroup = new KubernetesServerGroup(serverGroup.statefulSet, accountName, events) relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) - relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) relationships[Keys.Namespace.INSTANCES.ns].addAll(instanceKeys) } } @@ -344,12 +323,5 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement boolean exists() { statefulSet } - - List getLoadBalancers() { - KubernetesUtil.getLoadBalancers(statefulSet) - } - - - } } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy index 17ccb38ea7c..1582ae7b6e0 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy @@ -79,6 +79,11 @@ class KubernetesProviderUtils { KubernetesServerGroup serverGroup = objectMapper.convertValue(cacheData.attributes.serverGroup, KubernetesServerGroup) serverGroup.instances = instances serverGroup.deploymentStatus = deployment ? new KubernetesDeploymentStatus(deployment) : null + /** + * We are checking Null here because in the KubernetesServerGroup for statefullset we don't have deployDescription yet + * It will be available in next code checkin then will remove this Null check + */ + if(serverGroup.deployDescription!=null) serverGroup.deployDescription.deployment = KubernetesApiConverter.fromDeployment(deployment) return serverGroup } From 38abb93a76ac04d28f1f75362392bf8c8a978dc6 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Aug 2017 11:07:56 +0530 Subject: [PATCH 03/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (Statefulset Part 1) --- .../provider/agent/KubernetesControllersCachingAgent.groovy | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy index 13b1b451967..c731d708c57 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy @@ -26,7 +26,6 @@ import com.netflix.spinnaker.cats.cache.CacheData import com.netflix.spinnaker.cats.provider.ProviderCache import com.netflix.spinnaker.clouddriver.cache.OnDemandAgent import com.netflix.spinnaker.clouddriver.cache.OnDemandMetricsSupport -import com.netflix.spinnaker.clouddriver.kubernetes.deploy.KubernetesUtil import com.netflix.spinnaker.clouddriver.kubernetes.model.KubernetesServerGroup import com.netflix.spinnaker.clouddriver.kubernetes.provider.view.MutableCacheData import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials @@ -35,7 +34,6 @@ import com.netflix.spinnaker.clouddriver.kubernetes.cache.Keys import com.netflix.spectator.api.Registry import groovy.util.logging.Slf4j import io.fabric8.kubernetes.api.model.Event -import io.fabric8.kubernetes.api.model.HorizontalPodAutoscaler import io.kubernetes.client.models.V1beta1StatefulSet /** @@ -86,8 +84,6 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement def serverGroupName = data.serverGroupName.toString() - - V1beta1StatefulSet statefulSet = metricsSupport.readData { loadStatefulSets() } From 56b28ed37774ddda9b6954e47b325fb52bbf5b6c Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Aug 2017 11:18:49 +0530 Subject: [PATCH 04/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (Statefulset Part 1) Removed Unwanted code --- .../agent/KubernetesServerGroupCachingAgent.groovy | 8 -------- 1 file changed, 8 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesServerGroupCachingAgent.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesServerGroupCachingAgent.groovy index d0da950a455..f99d2f1d385 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesServerGroupCachingAgent.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesServerGroupCachingAgent.groovy @@ -39,7 +39,6 @@ import io.fabric8.kubernetes.api.model.Pod import io.fabric8.kubernetes.api.model.ReplicationController import io.fabric8.kubernetes.api.model.HorizontalPodAutoscaler import io.fabric8.kubernetes.api.model.extensions.ReplicaSet -import io.kubernetes.client.models.V1beta1StatefulSet import static com.netflix.spinnaker.cats.agent.AgentDataType.Authority.AUTHORITATIVE import static com.netflix.spinnaker.cats.agent.AgentDataType.Authority.INFORMATIVE @@ -198,12 +197,6 @@ class KubernetesServerGroupCachingAgent extends KubernetesCachingAgent implement }.flatten() } - List loadStatefulSets() { - namespaces.collect { String namespace -> - credentials.apiClientAdaptor.getStatefulSets(namespace) - }.flatten() - } - ReplicaSet loadReplicaSet(String namespace, String name) { credentials.apiAdaptor.getReplicaSet(namespace, name) } @@ -222,7 +215,6 @@ class KubernetesServerGroupCachingAgent extends KubernetesCachingAgent implement Long start = System.currentTimeMillis() List replicationControllerList = loadReplicationControllers() List replicaSetList = loadReplicaSets() - List statefulSet = loadStatefulSets() List serverGroups = (replicationControllerList.collect { it ? new ReplicaSetOrController(replicationController: it) : null } + replicaSetList.collect { From 91d577bbeab165c3e69a16c716e79513a5b9f5c9 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 21 Aug 2017 19:19:54 +0530 Subject: [PATCH 05/13] feature(provider/kubernetes) : Indentation Issue Fixed --- .../kubernetes/model/KubernetesServerGroup.groovy | 14 +++++++------- .../provider/view/KubernetesProviderUtils.groovy | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy index 0825ba2a602..a8fc3560e58 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy @@ -68,16 +68,16 @@ class KubernetesServerGroup implements ServerGroup, Serializable { * I have added a null check as in statefullset deployDescription is null */ if (deployDescription != null) { - for (def container : this.deployDescription.containers) { - imageList.add(KubernetesUtil.getImageIdWithoutRegistry(container.imageDescription)) - } + for (def container : this.deployDescription.containers) { + imageList.add(KubernetesUtil.getImageIdWithoutRegistry(container.imageDescription)) + } - buildInfo.images = imageList + buildInfo.images = imageList - def parsedName = Names.parseName(name) + def parsedName = Names.parseName(name) - buildInfo.createdBy = this.deployDescription?.deployment?.enabled ? parsedName.cluster : null - } + buildInfo.createdBy = this.deployDescription?.deployment?.enabled ? parsedName.cluster : null + } return buildInfo } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy index 1582ae7b6e0..5ba83b54a0b 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/view/KubernetesProviderUtils.groovy @@ -83,8 +83,8 @@ class KubernetesProviderUtils { * We are checking Null here because in the KubernetesServerGroup for statefullset we don't have deployDescription yet * It will be available in next code checkin then will remove this Null check */ - if(serverGroup.deployDescription!=null) - serverGroup.deployDescription.deployment = KubernetesApiConverter.fromDeployment(deployment) + if (serverGroup.deployDescription != null) + serverGroup.deployDescription.deployment = KubernetesApiConverter.fromDeployment(deployment) return serverGroup } } From bdbcca45fb501b56442950c5ede912c28aaa908f Mon Sep 17 00:00:00 2001 From: root Date: Tue, 22 Aug 2017 17:01:24 +0530 Subject: [PATCH 06/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (Statefulset Part 2) --- .../api/KubernetesClientApiConverter.groovy | 274 ++++++++++++++++++ .../model/KubernetesServerGroup.groovy | 3 +- 2 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy new file mode 100644 index 00000000000..009b5f5adee --- /dev/null +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -0,0 +1,274 @@ +package com.netflix.spinnaker.clouddriver.kubernetes.api + +import com.netflix.frigga.Names +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.KubernetesUtil +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.DeployKubernetesAtomicOperationDescription +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesAwsElasticBlockStoreVolumeSource +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesCapabilities +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesConfigMapVolumeSource +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesContainerDescription +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesContainerPort +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesEmptyDir +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesHandler +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesHandlerType +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesHostPath +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesKeyToPath +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesLifecycle +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesPersistentVolumeClaim +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesProbe +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesPullPolicy +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesResourceDescription +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesSeLinuxOptions +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesSecretVolumeSource +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesSecurityContext +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesStorageMediumType +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesVolumeMount +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesVolumeSource +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesVolumeSourceType +import io.fabric8.kubernetes.api.model.KeyToPath +import io.kubernetes.client.models.V1Container +import io.kubernetes.client.models.V1Probe +import io.kubernetes.client.models.V1Volume +import io.kubernetes.client.models.V1beta1StatefulSet + +/** + * Created by spinnaker on 20/8/17. + */ +class KubernetesClientApiConverter { + + + static DeployKubernetesAtomicOperationDescription fromStatefulSet(V1beta1StatefulSet statefulSet) { + def deployDescription = new DeployKubernetesAtomicOperationDescription() + def parsedName = Names.parseName(statefulSet?.metadata?.name) + + deployDescription.application = parsedName?.app + deployDescription.stack = parsedName?.stack + deployDescription.freeFormDetails = parsedName?.detail + /** + * Will fetch this values in next Part of checkin , when required + */ +// deployDescription.loadBalancers = KubernetesUtil?.getLoadBalancers(statefulSet) + deployDescription.namespace = statefulSet?.metadata?.namespace + deployDescription.targetSize = statefulSet?.spec?.replicas + deployDescription.securityGroups = [] + deployDescription.replicaSetAnnotations = statefulSet?.metadata?.annotations + deployDescription.podAnnotations = statefulSet?.spec?.template?.metadata?.annotations + + deployDescription.volumeSources = statefulSet?.spec?.template?.spec?.volumes?.collect { + fromVolume(it) + } ?: [] + + deployDescription.hostNetwork = statefulSet?.spec?.template?.spec?.hostNetwork + + deployDescription.containers = statefulSet?.spec?.template?.spec?.containers?.collect { + fromContainer(it) + } ?: [] + + deployDescription.terminationGracePeriodSeconds = statefulSet?.spec?.template?.spec?.terminationGracePeriodSeconds + deployDescription.serviceAccountName = statefulSet?.spec?.template?.spec?.serviceAccountName + + deployDescription.nodeSelector = statefulSet?.spec?.template?.spec?.nodeSelector + + return deployDescription + } + + + static KubernetesContainerDescription fromContainer(V1Container container) { + if (!container) { + return null + } + + def containerDescription = new KubernetesContainerDescription() + containerDescription.name = container.name + containerDescription.imageDescription = KubernetesUtil.buildImageDescription(container.image) + + if (container.imagePullPolicy) { + containerDescription.imagePullPolicy = KubernetesPullPolicy.valueOf(container.imagePullPolicy) + } + + container.resources?.with { + containerDescription.limits = limits?.cpu || limits?.memory ? + new KubernetesResourceDescription( + cpu: limits?.cpu, + memory: limits?.memory + ) : null + + containerDescription.requests = requests?.cpu || requests?.memory ? + new KubernetesResourceDescription( + cpu: requests?.cpu, + memory: requests?.memory + ) : null + } + + if (container.lifecycle) { + containerDescription.lifecycle = new KubernetesLifecycle() + if (container.lifecycle.postStart) { + containerDescription.lifecycle.postStart = fromHandler(container.lifecycle.postStart) + } + if (container.lifecycle.preStop) { + containerDescription.lifecycle.preStop = fromHandler(container.lifecycle.preStop) + } + } + + containerDescription.ports = container.ports?.collect { + def port = new KubernetesContainerPort() + port.hostIp = it?.hostIP + if (it?.hostPort) { + port.hostPort = it?.hostPort?.intValue() + } + if (it?.containerPort) { + port.containerPort = it?.containerPort?.intValue() + } + port.name = it?.name + port.protocol = it?.protocol + + return port + } + + if (container.securityContext) { + def securityContext = container.securityContext + + containerDescription.securityContext = new KubernetesSecurityContext(privileged: securityContext.privileged, + runAsNonRoot: securityContext.runAsNonRoot, + runAsUser: securityContext.runAsUser, + readOnlyRootFilesystem: securityContext.readOnlyRootFilesystem + ) + + if (securityContext.capabilities) { + def capabilities = securityContext.capabilities + + containerDescription.securityContext.capabilities = new KubernetesCapabilities(add: capabilities.add, drop: capabilities.drop) + } + + if (securityContext.seLinuxOptions) { + def seLinuxOptions = securityContext.seLinuxOptions + + containerDescription.securityContext.seLinuxOptions = new KubernetesSeLinuxOptions(user: seLinuxOptions.user, + role: seLinuxOptions.role, + type: seLinuxOptions.type, + level: seLinuxOptions.level + ) + } + } + + containerDescription.livenessProbe = fromV1Probe(container?.livenessProbe) + containerDescription.readinessProbe = fromV1Probe(container?.readinessProbe) + + /*containerDescription.envVars = container?.env?.collect { envVar -> + def result = new KubernetesEnvVar(name: envVar.name) + if (envVar.value) { + result.value = envVar.value + } else if (envVar.valueFrom) { + def source = new KubernetesEnvVarSource() + if (envVar.valueFrom.configMapKeyRef) { + def configMap = envVar.valueFrom.configMapKeyRef + source.configMapSource = new KubernetesConfigMapSource(key: configMap.key, configMapName: configMap.name) + } else if (envVar.valueFrom.secretKeyRef) { + def secret = envVar.valueFrom.secretKeyRef + source.secretSource = new KubernetesSecretSource(key: secret.key, secretName: secret.name) + } else if (envVar.valueFrom.fieldRef) { + def fieldPath = envVar.valueFrom.fieldRef.fieldPath; + source.fieldRef = new KubernetesFieldRefSource(fieldPath: fieldPath) + } else if (envVar.valueFrom.resourceFieldRef) { + def resource = envVar.valueFrom.resourceFieldRef.resource + def containerName = envVar.valueFrom.resourceFieldRef.containerName + def divisor = envVar.valueFrom.resourceFieldRef.divisor + source.resourceFieldRef = new KubernetesResourceFieldRefSource(resource: resource, + containerName: containerName, + divisor: divisor) + } else { + return null + } + result.envSource = source + } else { + return null + } + return result + } - null*/ + + containerDescription.volumeMounts = container?.volumeMounts?.collect { volumeMount -> + new KubernetesVolumeMount(name: volumeMount.name, readOnly: volumeMount.readOnly, mountPath: volumeMount.mountPath) + } + + containerDescription.args = container?.args ?: [] + containerDescription.command = container?.command ?: [] + + return containerDescription + } + + static KubernetesVolumeSource fromVolume(V1Volume volume) { + def res = new KubernetesVolumeSource(name: volume.name) + + if (volume.emptyDir) { + res.type = KubernetesVolumeSourceType.EmptyDir + def medium = volume.emptyDir.medium + def mediumType + + if (medium == "Memory") { + mediumType = KubernetesStorageMediumType.Memory + } else { + mediumType = KubernetesStorageMediumType.Default + } + + res.emptyDir = new KubernetesEmptyDir(medium: mediumType) + } else if (volume.hostPath) { + res.type = KubernetesVolumeSourceType.HostPath + res.hostPath = new KubernetesHostPath(path: volume.hostPath.path) + } else if (volume.persistentVolumeClaim) { + res.type = KubernetesVolumeSourceType.PersistentVolumeClaim + res.persistentVolumeClaim = new KubernetesPersistentVolumeClaim(claimName: volume.persistentVolumeClaim.claimName, + readOnly: volume.persistentVolumeClaim.readOnly) + } else if (volume.secret) { + res.type = KubernetesVolumeSourceType.Secret + res.secret = new KubernetesSecretVolumeSource(secretName: volume.secret.secretName) + } else if (volume.configMap) { + res.type = KubernetesVolumeSourceType.ConfigMap + def items = volume.configMap.items?.collect { KeyToPath item -> + new KubernetesKeyToPath(key: item.key, path: item.path) + } + res.configMap = new KubernetesConfigMapVolumeSource(configMapName: volume.configMap.name, items: items) + } else if (volume.awsElasticBlockStore) { + res.type = KubernetesVolumeSourceType.AwsElasticBlockStore + def ebs = volume.awsElasticBlockStore + res.awsElasticBlockStore = new KubernetesAwsElasticBlockStoreVolumeSource(volumeId: ebs.volumeID, + fsType: ebs.fsType, + partition: ebs.partition) + } else { + res.type = KubernetesVolumeSourceType.Unsupported + } + + return res + } + + + static KubernetesProbe fromV1Probe(V1Probe probe) { + if (!probe) { + return null + } + + def kubernetesProbe = new KubernetesProbe() + kubernetesProbe.failureThreshold = probe.failureThreshold ?: 0 + kubernetesProbe.successThreshold = probe.successThreshold ?: 0 + kubernetesProbe.timeoutSeconds = probe.timeoutSeconds ?: 0 + kubernetesProbe.periodSeconds = probe.periodSeconds ?: 0 + kubernetesProbe.initialDelaySeconds = probe.initialDelaySeconds ?: 0 + kubernetesProbe.handler = new KubernetesHandler() + + if (probe.exec) { + kubernetesProbe.handler.execAction = fromExecAction(probe.exec) + kubernetesProbe.handler.type = KubernetesHandlerType.EXEC + } + + if (probe.tcpSocket) { + kubernetesProbe.handler.tcpSocketAction = fromTcpSocketAction(probe.tcpSocket) + kubernetesProbe.handler.type = KubernetesHandlerType.TCP + } + + if (probe.httpGet) { + kubernetesProbe.handler.httpGetAction = fromHttpGetAction(probe.httpGet) + kubernetesProbe.handler.type = KubernetesHandlerType.HTTP + } + + return kubernetesProbe + } +} diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy index a8fc3560e58..0f9b5811bc7 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy @@ -20,6 +20,7 @@ import com.netflix.frigga.Names import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesCloudProvider import com.netflix.spinnaker.clouddriver.kubernetes.api.KubernetesApiAdaptor import com.netflix.spinnaker.clouddriver.kubernetes.api.KubernetesApiConverter +import com.netflix.spinnaker.clouddriver.kubernetes.api.KubernetesClientApiConverter import com.netflix.spinnaker.clouddriver.kubernetes.deploy.KubernetesUtil import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.DeployKubernetesAtomicOperationDescription import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesContainerDescription @@ -118,7 +119,7 @@ class KubernetesServerGroup implements ServerGroup, Serializable { this.replicas = statefulSet.spec?.replicas ?: 0 this.launchConfig = [:] this.labels = statefulSet.spec?.template?.metadata?.labels - this.deployDescription = null + this.deployDescription = KubernetesClientApiConverter.fromStatefulSet(statefulSet) this.kind = statefulSet.kind this.events = events?.collect { new KubernetesEvent(it) From a5e4a49d8d7ae9e470c0767fc41c9028337f01f5 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 22 Aug 2017 17:06:31 +0530 Subject: [PATCH 07/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (Statefulset Part 2) --- .../api/KubernetesClientApiConverter.groovy | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy index 009b5f5adee..7fb0e870590 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -1,3 +1,18 @@ +/* + * Copyright 2017 Cisco, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.netflix.spinnaker.clouddriver.kubernetes.api import com.netflix.frigga.Names From 7736c5fbb78daf80a3460d0a23972fe40aac216e Mon Sep 17 00:00:00 2001 From: root Date: Tue, 22 Aug 2017 17:27:04 +0530 Subject: [PATCH 08/13] feature(provider/kubernetes) : Adding support for Stateful set for new Caching agent using new java client library (Statefulset Part 2) --- .../api/KubernetesClientApiConverter.groovy | 92 ++++++++++++------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy index 7fb0e870590..3ca3c1852d6 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -45,6 +45,11 @@ import io.kubernetes.client.models.V1Container import io.kubernetes.client.models.V1Probe import io.kubernetes.client.models.V1Volume import io.kubernetes.client.models.V1beta1StatefulSet +import io.kubernetes.client.models.V1Handler +import io.kubernetes.client.models.V1ExecAction +import io.kubernetes.client.models.V1TCPSocketAction +import io.kubernetes.client.models.V1HTTPGetAction +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.* /** * Created by spinnaker on 20/8/17. @@ -169,37 +174,7 @@ class KubernetesClientApiConverter { containerDescription.livenessProbe = fromV1Probe(container?.livenessProbe) containerDescription.readinessProbe = fromV1Probe(container?.readinessProbe) - /*containerDescription.envVars = container?.env?.collect { envVar -> - def result = new KubernetesEnvVar(name: envVar.name) - if (envVar.value) { - result.value = envVar.value - } else if (envVar.valueFrom) { - def source = new KubernetesEnvVarSource() - if (envVar.valueFrom.configMapKeyRef) { - def configMap = envVar.valueFrom.configMapKeyRef - source.configMapSource = new KubernetesConfigMapSource(key: configMap.key, configMapName: configMap.name) - } else if (envVar.valueFrom.secretKeyRef) { - def secret = envVar.valueFrom.secretKeyRef - source.secretSource = new KubernetesSecretSource(key: secret.key, secretName: secret.name) - } else if (envVar.valueFrom.fieldRef) { - def fieldPath = envVar.valueFrom.fieldRef.fieldPath; - source.fieldRef = new KubernetesFieldRefSource(fieldPath: fieldPath) - } else if (envVar.valueFrom.resourceFieldRef) { - def resource = envVar.valueFrom.resourceFieldRef.resource - def containerName = envVar.valueFrom.resourceFieldRef.containerName - def divisor = envVar.valueFrom.resourceFieldRef.divisor - source.resourceFieldRef = new KubernetesResourceFieldRefSource(resource: resource, - containerName: containerName, - divisor: divisor) - } else { - return null - } - result.envSource = source - } else { - return null - } - return result - } - null*/ + containerDescription.volumeMounts = container?.volumeMounts?.collect { volumeMount -> new KubernetesVolumeMount(name: volumeMount.name, readOnly: volumeMount.readOnly, mountPath: volumeMount.mountPath) @@ -255,6 +230,61 @@ class KubernetesClientApiConverter { return res } + static KubernetesExecAction fromExecAction(V1ExecAction exec) { + if (!exec) { + return null + } + + def kubernetesExecAction = new KubernetesExecAction() + kubernetesExecAction.commands = exec.command + return kubernetesExecAction + } + + static KubernetesHandler fromHandler(V1Handler handler) { + def kubernetesHandler = new KubernetesHandler() + if (handler.exec) { + kubernetesHandler.execAction = fromExecAction(handler.exec) + kubernetesHandler.type = KubernetesHandlerType.EXEC + } + + if (handler.tcpSocket) { + kubernetesHandler.tcpSocketAction = fromTcpSocketAction(handler.tcpSocket) + kubernetesHandler.type = KubernetesHandlerType.TCP + } + + if (handler.httpGet) { + kubernetesHandler.httpGetAction = fromHttpGetAction(handler.httpGet) + kubernetesHandler.type = KubernetesHandlerType.HTTP + } + + return kubernetesHandler + } + + static KubernetesHttpGetAction fromHttpGetAction(V1HTTPGetAction httpGet) { + if (!httpGet) { + return null + } + + def kubernetesHttpGetAction = new KubernetesHttpGetAction() + kubernetesHttpGetAction.host = httpGet.host + kubernetesHttpGetAction.path = httpGet.path + kubernetesHttpGetAction.port = httpGet.port?.intVal ?: 0 + kubernetesHttpGetAction.uriScheme = httpGet.scheme + kubernetesHttpGetAction.httpHeaders = httpGet.httpHeaders?.collect() { + new KeyValuePair(name: it.name, value: it.value) + } + return kubernetesHttpGetAction + } + + static KubernetesTcpSocketAction fromTcpSocketAction(V1TCPSocketAction tcpSocket) { + if (!tcpSocket) { + return null + } + + def kubernetesTcpSocketAction = new KubernetesTcpSocketAction() + kubernetesTcpSocketAction.port = tcpSocket.port ?: 0 + return kubernetesTcpSocketAction + } static KubernetesProbe fromV1Probe(V1Probe probe) { if (!probe) { From 00ec201c5a9cd09f1ec27501aaae08e19a23d68b Mon Sep 17 00:00:00 2001 From: root Date: Wed, 23 Aug 2017 15:25:21 +0530 Subject: [PATCH 09/13] feature(provider/kubernetes) : In order to see Statefulset in Spinnaker UI we need to have loadbalancer details also as its throw null pointer at com.netflix.spinnaker.clouddriver.controllers.ApplicationsController on line no: 101 (result.clusters[account] << new ApplicationClusterViewModel(name: cluster.name, loadBalancers: cluster.loadBalancers.name as TreeSet, serverGroups: cluster.serverGroups*.name as TreeSet, provider: cluster.type) at this call 'cluster.loadBalancers.name') --- .../api/KubernetesClientApiConverter.groovy | 9 +++++---- .../agent/KubernetesControllersCachingAgent.groovy | 13 ++++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy index 3ca3c1852d6..49154cd6d1f 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -40,6 +40,10 @@ import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergro import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesVolumeMount import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesVolumeSource import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesVolumeSourceType +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesExecAction +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesHttpGetAction +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KeyValuePair +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesTcpSocketAction import io.fabric8.kubernetes.api.model.KeyToPath import io.kubernetes.client.models.V1Container import io.kubernetes.client.models.V1Probe @@ -49,14 +53,13 @@ import io.kubernetes.client.models.V1Handler import io.kubernetes.client.models.V1ExecAction import io.kubernetes.client.models.V1TCPSocketAction import io.kubernetes.client.models.V1HTTPGetAction -import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.* + /** * Created by spinnaker on 20/8/17. */ class KubernetesClientApiConverter { - static DeployKubernetesAtomicOperationDescription fromStatefulSet(V1beta1StatefulSet statefulSet) { def deployDescription = new DeployKubernetesAtomicOperationDescription() def parsedName = Names.parseName(statefulSet?.metadata?.name) @@ -174,8 +177,6 @@ class KubernetesClientApiConverter { containerDescription.livenessProbe = fromV1Probe(container?.livenessProbe) containerDescription.readinessProbe = fromV1Probe(container?.readinessProbe) - - containerDescription.volumeMounts = container?.volumeMounts?.collect { volumeMount -> new KubernetesVolumeMount(name: volumeMount.name, readOnly: volumeMount.readOnly, mountPath: volumeMount.mountPath) } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy index c731d708c57..91004038dca 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy @@ -26,6 +26,7 @@ import com.netflix.spinnaker.cats.cache.CacheData import com.netflix.spinnaker.cats.provider.ProviderCache import com.netflix.spinnaker.clouddriver.cache.OnDemandAgent import com.netflix.spinnaker.clouddriver.cache.OnDemandMetricsSupport +import com.netflix.spinnaker.clouddriver.kubernetes.deploy.KubernetesUtil import com.netflix.spinnaker.clouddriver.kubernetes.model.KubernetesServerGroup import com.netflix.spinnaker.clouddriver.kubernetes.provider.view.MutableCacheData import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials @@ -240,17 +241,21 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement def applicationKey = Keys.getApplicationKey(applicationName) def clusterKey = Keys.getClusterKey(accountName, applicationName, category, clusterName) def instanceKeys = [] - + def loadBalancerKeys = serverGroup.loadBalancers.collect({ + Keys.getLoadBalancerKey(accountName, serverGroup.namespace, it) + }) cachedApplications[applicationKey].with { attributes.name = applicationName relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) relationships[Keys.Namespace.SERVER_GROUPS.ns].add(serverGroupKey) + relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) } cachedClusters[clusterKey].with { attributes.name = clusterName relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) relationships[Keys.Namespace.SERVER_GROUPS.ns].add(serverGroupKey) + relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) } cachedServerGroups[serverGroupKey].with { @@ -265,6 +270,7 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) relationships[Keys.Namespace.INSTANCES.ns].addAll(instanceKeys) + relationships[Keys.Namespace.LOAD_BALANCERS.ns].addAll(loadBalancerKeys) } } } @@ -319,5 +325,10 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement boolean exists() { statefulSet } + + List getLoadBalancers() { + KubernetesUtil.getLoadBalancers(statefulSet.spec?.template?.metadata?.labels ?: [:]) + } + } } From a4ef89b6896388615d2e1e4cb86f0d3b07d27c38 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 23 Aug 2017 15:59:34 +0530 Subject: [PATCH 10/13] feature(provider/kubernetes) : Added Persistance Volume Claim for StatefulSet --- .../kubernetes/api/KubernetesClientApiConverter.groovy | 8 ++------ .../DeployKubernetesAtomicOperationDescription.groovy | 2 ++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy index 49154cd6d1f..0eead6b28c1 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -70,26 +70,22 @@ class KubernetesClientApiConverter { /** * Will fetch this values in next Part of checkin , when required */ -// deployDescription.loadBalancers = KubernetesUtil?.getLoadBalancers(statefulSet) + deployDescription.loadBalancers = KubernetesUtil?.getLoadBalancers(statefulSet.spec?.template?.metadata?.labels ?: [:]) deployDescription.namespace = statefulSet?.metadata?.namespace deployDescription.targetSize = statefulSet?.spec?.replicas deployDescription.securityGroups = [] deployDescription.replicaSetAnnotations = statefulSet?.metadata?.annotations deployDescription.podAnnotations = statefulSet?.spec?.template?.metadata?.annotations - + deployDescription.volumeClaimList = statefulSet?.spec?.getVolumeClaimTemplates() deployDescription.volumeSources = statefulSet?.spec?.template?.spec?.volumes?.collect { fromVolume(it) } ?: [] - deployDescription.hostNetwork = statefulSet?.spec?.template?.spec?.hostNetwork - deployDescription.containers = statefulSet?.spec?.template?.spec?.containers?.collect { fromContainer(it) } ?: [] - deployDescription.terminationGracePeriodSeconds = statefulSet?.spec?.template?.spec?.terminationGracePeriodSeconds deployDescription.serviceAccountName = statefulSet?.spec?.template?.spec?.serviceAccountName - deployDescription.nodeSelector = statefulSet?.spec?.template?.spec?.nodeSelector return deployDescription diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy index 7d2a0b340a6..9c155809727 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy @@ -22,6 +22,7 @@ import com.netflix.spinnaker.clouddriver.deploy.DeployDescription import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.KubernetesAtomicOperationDescription import groovy.transform.AutoClone import groovy.transform.Canonical +import io.kubernetes.client.models.V1PersistentVolumeClaim @AutoClone @Canonical @@ -48,6 +49,7 @@ class DeployKubernetesAtomicOperationDescription extends KubernetesAtomicOperati String serviceAccountName Integer sequence KubernetesPodSpecDescription podSpec + List volumeClaimList @JsonIgnore Set imagePullSecrets From 0ece2bdad3f55d42c1d6059b17017e91950a53ce Mon Sep 17 00:00:00 2001 From: root Date: Wed, 23 Aug 2017 21:57:04 +0530 Subject: [PATCH 11/13] feature(provider/kubernetes) : 1) Removed V1PersistentVolumeClaim (Added the right implementation) 2) Removed extra Space --- .../api/KubernetesClientApiConverter.groovy | 3 +- ...ubernetesAtomicOperationDescription.groovy | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy index 0eead6b28c1..6cd5c190084 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -54,7 +54,6 @@ import io.kubernetes.client.models.V1ExecAction import io.kubernetes.client.models.V1TCPSocketAction import io.kubernetes.client.models.V1HTTPGetAction - /** * Created by spinnaker on 20/8/17. */ @@ -76,7 +75,7 @@ class KubernetesClientApiConverter { deployDescription.securityGroups = [] deployDescription.replicaSetAnnotations = statefulSet?.metadata?.annotations deployDescription.podAnnotations = statefulSet?.spec?.template?.metadata?.annotations - deployDescription.volumeClaimList = statefulSet?.spec?.getVolumeClaimTemplates() + deployDescription.volumeClaims = statefulSet?.spec?.getVolumeClaimTemplates() deployDescription.volumeSources = statefulSet?.spec?.template?.spec?.volumes?.collect { fromVolume(it) } ?: [] diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy index 9c155809727..e37b5bc3e48 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/deploy/description/servergroup/DeployKubernetesAtomicOperationDescription.groovy @@ -22,7 +22,6 @@ import com.netflix.spinnaker.clouddriver.deploy.DeployDescription import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.KubernetesAtomicOperationDescription import groovy.transform.AutoClone import groovy.transform.Canonical -import io.kubernetes.client.models.V1PersistentVolumeClaim @AutoClone @Canonical @@ -49,7 +48,7 @@ class DeployKubernetesAtomicOperationDescription extends KubernetesAtomicOperati String serviceAccountName Integer sequence KubernetesPodSpecDescription podSpec - List volumeClaimList + List volumeClaims @JsonIgnore Set imagePullSecrets @@ -424,3 +423,36 @@ class KubernetesPodSpecDescription { Long terminationGracePeriodSeconds List volumeSources } + +@AutoClone +@Canonical +class KubernetesPersistentVolumeClaimDescription { + String claimName + List accessModes + KubernetesPeristentResourceRequirement requirements + KubernetesPersistentLabelSelector selector + String storageClassName +} + +@AutoClone +@Canonical +class KubernetesPeristentResourceRequirement { + Map limits + Map requests +} + +@AutoClone +@Canonical +class KubernetesPersistentLabelSelector { + List matchExpressions + Map matchLabels +} + +@AutoClone +@Canonical +class KubernetesLabelSelectorRequirements { + String key + String operator + List values +} + From b26485c289b15ec33697c136ffcbd41898891877 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Aug 2017 17:44:24 +0530 Subject: [PATCH 12/13] feature(provider/kubernetes) : 1) Adding DaemonSets Support (Part 1)2) in order to accommodate new controller , created new generic KubernatesController in KubernetesControllersCachingAgent --- .../api/KubernetesClientApiAdapter.groovy | 14 ++++++ .../api/KubernetesClientApiConverter.groovy | 4 +- .../model/KubernetesServerGroup.groovy | 25 ++++++++++ .../KubernetesControllersCachingAgent.groovy | 49 +++++++++++++------ 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy index 1345ae3fa1f..86eef071304 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy @@ -25,6 +25,7 @@ import io.kubernetes.client.ApiException import io.kubernetes.client.Configuration import io.kubernetes.client.apis.AppsV1beta1Api import io.kubernetes.client.models.* +import io.kubernetes.client.apis.ExtensionsV1beta1Api import java.util.concurrent.TimeUnit @@ -40,6 +41,7 @@ class KubernetesClientApiAdapter { final Clock spectatorClock final ApiClient client final AppsV1beta1Api apiInstance + final ExtensionsV1beta1Api extApi public spectatorRegistry() { return spectatorRegistry } @@ -55,6 +57,7 @@ class KubernetesClientApiAdapter { client = config.getApiCient() Configuration.setDefaultApiClient(client) apiInstance = new AppsV1beta1Api(); + extApi = new ExtensionsV1beta1Api() } KubernetesOperationException formatException(String operation, String namespace, ApiException e) { @@ -134,4 +137,15 @@ class KubernetesClientApiAdapter { return statefulSets } } + + List getDaemonSets(String namespace) { + exceptionWrapper("daemonSets.list", "Get Daemon Sets", namespace) { + V1beta1DaemonSetList list = extApi.listNamespacedDaemonSet(namespace, null, null, null, null, API_CALL_TIMEOUT_SECONDS, null) + List daemonSet = new ArrayList() + list.items?.forEach({ item -> + daemonSet.add(item) + }) + return daemonSet + } + } } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy index 6cd5c190084..3fbba8678fe 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiConverter.groovy @@ -44,7 +44,7 @@ import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergro import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesHttpGetAction import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KeyValuePair import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.KubernetesTcpSocketAction -import io.fabric8.kubernetes.api.model.KeyToPath +import io.kubernetes.client.models.V1KeyToPath import io.kubernetes.client.models.V1Container import io.kubernetes.client.models.V1Probe import io.kubernetes.client.models.V1Volume @@ -209,7 +209,7 @@ class KubernetesClientApiConverter { res.secret = new KubernetesSecretVolumeSource(secretName: volume.secret.secretName) } else if (volume.configMap) { res.type = KubernetesVolumeSourceType.ConfigMap - def items = volume.configMap.items?.collect { KeyToPath item -> + def items = volume.configMap.items?.collect { V1KeyToPath item -> new KubernetesKeyToPath(key: item.key, path: item.path) } res.configMap = new KubernetesConfigMapVolumeSource(configMapName: volume.configMap.name, items: items) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy index 0f9b5811bc7..e6eb51eb7e0 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy @@ -33,6 +33,7 @@ import io.fabric8.kubernetes.api.model.ReplicationController import io.fabric8.kubernetes.api.model.HorizontalPodAutoscaler import io.fabric8.kubernetes.api.model.extensions.ReplicaSet import io.fabric8.kubernetes.client.internal.SerializationUtils +import io.kubernetes.client.models.V1beta1DaemonSet import io.kubernetes.client.models.V1beta1StatefulSet @CompileStatic @@ -126,6 +127,30 @@ class KubernetesServerGroup implements ServerGroup, Serializable { } } + KubernetesServerGroup(V1beta1DaemonSet daemonSet, String account, List events) { + this.name = daemonSet.metadata?.name + this.account = account + this.region = daemonSet.metadata?.namespace + this.namespace = this.region + this.createdTime = daemonSet.metadata?.creationTimestamp?.getMillis() + this.zones = [this.region] as Set + this.securityGroups = [] + /** + * Need to check if this is required or not + */ + //this.replicas = daemonSet.spec?.replicas ?: 0 + this.launchConfig = [:] + this.labels = daemonSet.spec?.template?.metadata?.labels + /** + * Will fetch this valu in next Pull Request + */ + //this.deployDescription = KubernetesClientApiConverter.fromStatefulSet(daemonSet) + this.kind = daemonSet.kind + this.events = events?.collect { + new KubernetesEvent(it) + } + } + KubernetesServerGroup(ReplicaSet replicaSet, String account, List events, HorizontalPodAutoscaler autoscaler) { this.name = replicaSet.metadata?.name this.account = account diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy index 91004038dca..8ed7e844e3e 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/provider/agent/KubernetesControllersCachingAgent.groovy @@ -35,6 +35,7 @@ import com.netflix.spinnaker.clouddriver.kubernetes.cache.Keys import com.netflix.spectator.api.Registry import groovy.util.logging.Slf4j import io.fabric8.kubernetes.api.model.Event +import io.kubernetes.client.models.V1beta1DaemonSet import io.kubernetes.client.models.V1beta1StatefulSet /** @@ -89,8 +90,12 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement loadStatefulSets() } + V1beta1DaemonSet daemonSet = metricsSupport.readData { + loadDaemonSets() + } + CacheResult result = metricsSupport.transformData { - buildCacheResult([new StateFulSet(statefulSet: statefulSet)], [:], [], Long.MAX_VALUE) + buildCacheResult([new KubernetesController(controller: statefulSet,controller1: daemonSet)], [:], [], Long.MAX_VALUE) } def jsonResult = objectMapper.writeValueAsString(result.cacheResults) @@ -115,7 +120,7 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement } } // Evict this server group if it no longer exists. - Map> evictions = statefulSet ? [:] : [ + Map> evictions = statefulSet | daemonSet ? [:] : [ (Keys.Namespace.SERVER_GROUPS.ns): [ Keys.getServerGroupKey(accountName, namespace, serverGroupName) ] @@ -154,8 +159,11 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement reloadNamespaces() Long start = System.currentTimeMillis() List statefulSet = loadStatefulSets() - List serverGroups = (statefulSet.collect { - it ? new StateFulSet(statefulSet: it) : null + List daemonSet = loadDaemonSets() + List serverGroups = (statefulSet.collect { + it ? new KubernetesController(controller: it) : null + }+ daemonSet.collect { + it ? new KubernetesController(controller: it) : null } ) - null List evictFromOnDemand = [] @@ -198,7 +206,13 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement }.flatten() } - private CacheResult buildCacheResult(List serverGroups, Map onDemandKeep, List onDemandEvict, Long start) { + List loadDaemonSets() { + namespaces.collect { String namespace -> + credentials.apiClientAdaptor.getDaemonSets(namespace) + }.flatten() + } + + private CacheResult buildCacheResult(List serverGroups, Map onDemandKeep, List onDemandEvict, Long start) { log.info("Describing items in ${agentType}") Map cachedApplications = MutableCacheData.mutableCacheMap() @@ -208,17 +222,19 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement Map cachedLoadBalancers = MutableCacheData.mutableCacheMap() Map> stateFulsetEvents = [:].withDefault { _ -> [:] } + Map> daemonsetEvents = [:].withDefault { _ -> [:] } try { namespaces.each { String namespace -> stateFulsetEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, "V1beta1StatefulSet") + daemonsetEvents[namespace] = credentials.apiAdaptor.getEvents(namespace, "V1beta1DaemonSet") } } catch (Exception e) { log.warn "Failure fetching events for all server groups in $namespaces", e } - for (StateFulSet serverGroup: serverGroups) { + for (KubernetesController serverGroup: serverGroups) { if (!serverGroup.exists()) { continue } @@ -262,11 +278,12 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement def events = null attributes.name = serverGroupName - if (serverGroup.statefulSet) { - + if (serverGroup.controller instanceof V1beta1StatefulSet) { events = stateFulsetEvents[serverGroup.namespace][serverGroupName] + } else if (serverGroup.controller instanceof V1beta1DaemonSet) { + events = daemonsetEvents[serverGroup.namespace][serverGroupName] } - attributes.serverGroup = new KubernetesServerGroup(serverGroup.statefulSet, accountName, events) + attributes.serverGroup = new KubernetesServerGroup(serverGroup.controller, accountName, events) relationships[Keys.Namespace.APPLICATIONS.ns].add(applicationKey) relationships[Keys.Namespace.CLUSTERS.ns].add(clusterKey) relationships[Keys.Namespace.INSTANCES.ns].addAll(instanceKeys) @@ -307,27 +324,27 @@ class KubernetesControllersCachingAgent extends KubernetesCachingAgent implement } } - class StateFulSet{ + class KubernetesController{ - V1beta1StatefulSet statefulSet + def controller String getName() { - statefulSet.metadata.name + controller.metadata.name } String getNamespace() { - statefulSet.metadata.namespace + controller.metadata.namespace } Map getSelector() { - statefulSet.spec.selector.matchLabels + controller.spec.selector.matchLabels } boolean exists() { - statefulSet + controller } List getLoadBalancers() { - KubernetesUtil.getLoadBalancers(statefulSet.spec?.template?.metadata?.labels ?: [:]) + KubernetesUtil.getLoadBalancers(controller.spec?.template?.metadata?.labels ?: [:]) } } From fd5c378ab2011b299f954bcdcdc1a3b647a70132 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Aug 2017 00:03:45 +0530 Subject: [PATCH 13/13] feature(provider/kubernetes) : 1) Incorporated the Review comments (Pull Request 1831) 2) DaemonSetsSpec doesnot support Replicas : Checked the Documentation (V1beta1DaemonSetSpec.md) --- .../kubernetes/api/KubernetesClientApiAdapter.groovy | 8 ++------ .../kubernetes/model/KubernetesServerGroup.groovy | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy index 86eef071304..6b72051c7a1 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/api/KubernetesClientApiAdapter.groovy @@ -25,7 +25,7 @@ import io.kubernetes.client.ApiException import io.kubernetes.client.Configuration import io.kubernetes.client.apis.AppsV1beta1Api import io.kubernetes.client.models.* -import io.kubernetes.client.apis.ExtensionsV1beta1Api +import io.kubernetes.client.apis.ExtensionsV1beta1Api import java.util.concurrent.TimeUnit @@ -141,11 +141,7 @@ class KubernetesClientApiAdapter { List getDaemonSets(String namespace) { exceptionWrapper("daemonSets.list", "Get Daemon Sets", namespace) { V1beta1DaemonSetList list = extApi.listNamespacedDaemonSet(namespace, null, null, null, null, API_CALL_TIMEOUT_SECONDS, null) - List daemonSet = new ArrayList() - list.items?.forEach({ item -> - daemonSet.add(item) - }) - return daemonSet + return list.items } } } diff --git a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy index e6eb51eb7e0..f4f655ea66a 100644 --- a/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy +++ b/clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/model/KubernetesServerGroup.groovy @@ -136,7 +136,8 @@ class KubernetesServerGroup implements ServerGroup, Serializable { this.zones = [this.region] as Set this.securityGroups = [] /** - * Need to check if this is required or not + * DaemonSetsSpec doesnot support Replicas : Checked the Documentation (V1beta1DaemonSetSpec.md) + * Thats why i think its not required */ //this.replicas = daemonSet.spec?.replicas ?: 0 this.launchConfig = [:]