Skip to content

Commit

Permalink
provider/kubernetes: Upsert load balancer
Browse files Browse the repository at this point in the history
  • Loading branch information
Lars Wander committed Feb 12, 2016
1 parent daf3ec3 commit e6db61f
Show file tree
Hide file tree
Showing 26 changed files with 917 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ class KubernetesApiAdaptor {
client.services().inNamespace(namespace).withName(service).get()
}

Service getSecurityGroup(String namespace, String securityGroup) {
getService(namespace, securityGroup)
Service createService(String namespace, Service service) {
client.services().inNamespace(namespace).create(service)
}

Service getLoadBalancer(String namespace, String loadBalancer) {
getService(namespace, loadBalancer)
Service replaceService(String namespace, String name, Service service) {
client.services().inNamespace(namespace).withName(name).replace(service)
}

Secret getSecret(String namespace, String secret) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ package com.netflix.spinnaker.clouddriver.kubernetes.deploy

import com.netflix.frigga.NameValidation
import com.netflix.frigga.Names
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.exception.KubernetesIllegalArgumentException
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials
import io.fabric8.kubernetes.api.model.PodList
import io.fabric8.kubernetes.api.model.ReplicationController
import io.fabric8.kubernetes.api.model.ReplicationControllerList
import io.fabric8.kubernetes.api.model.Secret
import io.fabric8.kubernetes.api.model.Service

class KubernetesUtil {
static String SECURITY_GROUP_LABEL_PREFIX = "security-group-"
Expand Down Expand Up @@ -51,6 +48,20 @@ class KubernetesUtil {
rc.spec?.template?.spec?.imagePullSecrets?.collect({ it.name })
}

static String validateNamespace(KubernetesCredentials credentials, String namespace) {
def resolvedNamespace = namespace ?: "default"
if (!credentials.isRegisteredNamespace(resolvedNamespace)) {
def error = "Registered namespaces are ${credentials.getNamespaces()}."
if (namespace) {
error = "Namespace '$namespace' was not registered with provided credentials. $error"
} else {
error = "No provided namespace assumed to mean 'default' was not registered with provided credentials. $error"
}
throw new KubernetesIllegalArgumentException(error)
}
return resolvedNamespace
}

static List<String> getDescriptionLoadBalancers(ReplicationController rc) {
def loadBalancers = []
rc.spec?.template?.metadata?.labels?.each { key, val ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2016 Google, 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.deploy.converters.loadbalancer

import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.KubernetesAtomicOperationConverterHelper
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.loadbalancer.UpsertKubernetesLoadBalancerAtomicOperationDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.ops.loadbalancer.UpsertKubernetesLoadBalancerAtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations
import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport
import org.springframework.stereotype.Component

@KubernetesOperation(AtomicOperations.UPSERT_LOAD_BALANCER)
@Component
class UpsertKubernetesLoadBalancerAtomicOperationConverter extends AbstractAtomicOperationsCredentialsSupport {
AtomicOperation convertOperation(Map input) {
new UpsertKubernetesLoadBalancerAtomicOperation(convertDescription(input))
}

UpsertKubernetesLoadBalancerAtomicOperationDescription convertDescription(Map input) {
KubernetesAtomicOperationConverterHelper.convertDescription(input, this, UpsertKubernetesLoadBalancerAtomicOperationDescription)
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2016 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
* 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,
Expand All @@ -14,11 +14,12 @@
* limitations under the License.
*/

package com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters
package com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.servergroup

import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.CloneKubernetesAtomicOperationDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.ops.CloneKubernetesAtomicOperation
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.KubernetesAtomicOperationConverterHelper
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.CloneKubernetesAtomicOperationDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.ops.servergroup.CloneKubernetesAtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations
import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2015 Google, Inc.
* Copyright 2016 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
* 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,
Expand All @@ -14,11 +14,12 @@
* limitations under the License.
*/

package com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters
package com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.servergroup

import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.DeployKubernetesAtomicOperationDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.ops.DeployKubernetesAtomicOperation
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.KubernetesAtomicOperationConverterHelper
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup.DeployKubernetesAtomicOperationDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.ops.servergroup.DeployKubernetesAtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations
import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2016 Google, 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.deploy.description.loadbalancer

import com.netflix.spinnaker.clouddriver.deploy.DeployDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.KubernetesAtomicOperationDescription
import groovy.transform.AutoClone
import groovy.transform.Canonical

@AutoClone
@Canonical
class UpsertKubernetesLoadBalancerAtomicOperationDescription extends KubernetesAtomicOperationDescription implements DeployDescription {
String name
String namespace

List<KubernetesNamedServicePort> ports
List<String> externalIps

String type
}

@AutoClone
@Canonical
class KubernetesNamedServicePort {
String name
String protocol
int port
int targetPort
int nodePort
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2016 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
* 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,
Expand All @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.netflix.spinnaker.clouddriver.kubernetes.deploy.description
package com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup

import groovy.transform.Canonical

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright 2015 Google, Inc.
* Copyright 2016 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
* 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,
Expand All @@ -14,9 +14,10 @@
* limitations under the License.
*/

package com.netflix.spinnaker.clouddriver.kubernetes.deploy.description
package com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.servergroup

import com.netflix.spinnaker.clouddriver.deploy.DeployDescription
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.KubernetesAtomicOperationDescription
import groovy.transform.AutoClone
import groovy.transform.Canonical

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2016 Google, 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.deploy.ops.loadbalancer

import com.netflix.spinnaker.clouddriver.data.task.Task
import com.netflix.spinnaker.clouddriver.data.task.TaskRepository
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.KubernetesUtil
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.loadbalancer.KubernetesNamedServicePort
import com.netflix.spinnaker.clouddriver.kubernetes.deploy.description.loadbalancer.UpsertKubernetesLoadBalancerAtomicOperationDescription
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation
import io.fabric8.kubernetes.api.model.ServiceBuilder
import io.fabric8.kubernetes.api.model.ServicePort

class UpsertKubernetesLoadBalancerAtomicOperation implements AtomicOperation<Map> {
private static final String BASE_PHASE = "UPSERT_LOAD_BALANCER"

UpsertKubernetesLoadBalancerAtomicOperationDescription description

UpsertKubernetesLoadBalancerAtomicOperation(UpsertKubernetesLoadBalancerAtomicOperationDescription description) {
this.description = description
}

private static Task getTask() {
TaskRepository.threadLocalTask.get()
}

/*
* curl -X POST -H "Content-Type: application/json" -d '[ { "upsertLoadBalancer": { "name": "service", "ports": [ { "name": "http", "port": 80, "targetPort": 9376 } ], "credentials": "my-kubernetes-account" } } ]' localhost:7002/kubernetes/ops
*/
@Override
Map operate(List priorOutputs) {
task.updateStatus BASE_PHASE, "Initializing upsert of load balancer $description.name..."
task.updateStatus BASE_PHASE, "Looking up provided namespace..."

def credentials = description.kubernetesCredentials
def namespace = KubernetesUtil.validateNamespace(credentials, description.namespace)
def name = description.name

task.updateStatus BASE_PHASE, "Looking up existing load balancer..."
def existingService = credentials.apiAdaptor.getService(namespace, name)

if (existingService) {
task.updateStatus BASE_PHASE, "Found existing load balancer with name $description.name."
}

def serviceBuilder = new ServiceBuilder()

serviceBuilder = serviceBuilder.withNewMetadata().withName(name).endMetadata()

task.updateStatus BASE_PHASE, "Setting label selectors..."

serviceBuilder = serviceBuilder.withNewSpec()

serviceBuilder = serviceBuilder.addToSelector(KubernetesUtil.loadBalancerKey(name), 'true')

task.updateStatus BASE_PHASE, "Adding ports..."

List<KubernetesNamedServicePort> ports = []

for (ServicePort port : existingService?.spec?.ports) {
def namedPort = new KubernetesNamedServicePort()
port.name ? namedPort.name = port.name : null
port.nodePort ? namedPort.nodePort = port.nodePort : null
port.port ? namedPort.port = port.port : null
port.targetPort ? namedPort.targetPort = port.targetPort?.intVal : null
port.protocol ? namedPort.protocol = port.protocol : null
ports << namedPort
}

ports = description.ports != null ? description.ports : ports

for (def port : ports) {
serviceBuilder = serviceBuilder.addNewPort()

serviceBuilder = port.name ? serviceBuilder.withName(port.name) : serviceBuilder
serviceBuilder = port.targetPort ? serviceBuilder.withNewTargetPort(port.targetPort) : serviceBuilder
serviceBuilder = port.port ? serviceBuilder.withPort(port.targetPort) : serviceBuilder
serviceBuilder = port.nodePort ? serviceBuilder.withNodePort(port.targetPort) : serviceBuilder
serviceBuilder = port.protocol ? serviceBuilder.withProtocol(port.protocol) : serviceBuilder

serviceBuilder = serviceBuilder.endPort()
}

task.updateStatus BASE_PHASE, "Adding external IPs..."

def externalIps = description.externalIps != null ? description.externalIps : existingService?.spec?.externalIPs

for (def ip: externalIps) {
serviceBuilder = serviceBuilder.addToExternalIPs(ip)
}

task.updateStatus BASE_PHASE, "Setting type..."

def type = description.type != null ? description.type : existingService?.spec?.type

serviceBuilder = type ? serviceBuilder.withType(type) : serviceBuilder

serviceBuilder = serviceBuilder.endSpec()

def service = existingService ?
credentials.apiAdaptor.replaceService(namespace, name, serviceBuilder.build()) :
credentials.apiAdaptor.createService(namespace, serviceBuilder.build())

task.updateStatus BASE_PHASE, "Finished upserting load balancer $description.name."

[loadBalancers: [(service.metadata.namespace): [name: service.metadata.name]]]
}
}
Loading

0 comments on commit e6db61f

Please # to comment.