Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecs): Add service discovery support #3604

Merged
merged 8 commits into from May 6, 2019
Expand Up @@ -48,6 +48,8 @@
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
import com.amazonaws.services.cloudformation.AmazonCloudFormationClientBuilder;
import com.amazonaws.services.servicediscovery.AWSServiceDiscovery;
import com.amazonaws.services.servicediscovery.AWSServiceDiscoveryClientBuilder;
import com.amazonaws.services.shield.AWSShield;
import com.amazonaws.services.shield.AWSShieldClientBuilder;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
Expand Down Expand Up @@ -452,4 +454,8 @@ public AmazonECR getAmazonEcr(NetflixAmazonCredentials amazonCredentials, String
public AWSSecretsManager getAmazonSecretsManager(NetflixAmazonCredentials amazonCredentials, String region, boolean skipEdda) {
return proxyHandlerBuilder.getProxyHandler(AWSSecretsManager.class, AWSSecretsManagerClientBuilder.class, amazonCredentials, region, skipEdda);
}

public AWSServiceDiscovery getAmazonServiceDiscovery(NetflixAmazonCredentials amazonCredentials, String region, boolean skipEdda) {
return proxyHandlerBuilder.getProxyHandler(AWSServiceDiscovery.class, AWSServiceDiscoveryClientBuilder.class, amazonCredentials, region, skipEdda);
}
}
Expand Up @@ -35,7 +35,8 @@ public enum Namespace {
TASK_DEFINITIONS,
ALARMS,
SCALABLE_TARGETS,
SECRETS;
SECRETS,
SERVICE_DISCOVERY_REGISTRIES;

public final String ns;

Expand Down Expand Up @@ -124,6 +125,9 @@ public static Map<String, String> parse(String key) {
case SECRETS:
result.put("secretName", parts[4]);
break;
case SERVICE_DISCOVERY_REGISTRIES:
result.put("serviceId", parts[4]);
break;
case SCALABLE_TARGETS:
result.put("resource", parts[4]);
break;
Expand Down Expand Up @@ -179,6 +183,10 @@ public static String getSecretKey(String account, String region, String secretNa
return buildKey(Namespace.SECRETS.ns, account, region, secretName);
}

public static String getServiceDiscoveryRegistryKey(String account, String region, String registryId) {
return buildKey(Namespace.SERVICE_DISCOVERY_REGISTRIES.ns, account, region, registryId);
}

private static String buildKey(String namespace,String account, String region, String identifier){
return ID + SEPARATOR + namespace + SEPARATOR + account + SEPARATOR + region + SEPARATOR + identifier;
}
Expand Down
@@ -0,0 +1,49 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* This file 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.ecs.cache.client;

import com.netflix.spinnaker.cats.cache.Cache;
import com.netflix.spinnaker.cats.cache.CacheData;
import com.netflix.spinnaker.clouddriver.ecs.cache.model.ServiceDiscoveryRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

import static com.netflix.spinnaker.clouddriver.ecs.cache.Keys.Namespace.SERVICE_DISCOVERY_REGISTRIES;

@Component
public class ServiceDiscoveryCacheClient extends AbstractCacheClient<ServiceDiscoveryRegistry>{

@Autowired
public ServiceDiscoveryCacheClient(Cache cacheView) {
super(cacheView, SERVICE_DISCOVERY_REGISTRIES.toString());
}

@Override
protected ServiceDiscoveryRegistry convert(CacheData cacheData) {
ServiceDiscoveryRegistry registry = new ServiceDiscoveryRegistry();
Map<String, Object> attributes = cacheData.getAttributes();

registry.setAccount((String) attributes.get("account"));
registry.setRegion((String) attributes.get("region"));
registry.setName((String) attributes.get("serviceName"));
registry.setArn((String) attributes.get("serviceArn"));
registry.setId((String) attributes.get("serviceId"));

return registry;
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* This file 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.ecs.cache.model;

import lombok.Data;

@Data
public class ServiceDiscoveryRegistry {
String account;
String region;
String id;
String name;
String arn;
}
@@ -0,0 +1,41 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* This file 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.ecs.controllers;

import com.netflix.spinnaker.clouddriver.ecs.cache.model.ServiceDiscoveryRegistry;
import com.netflix.spinnaker.clouddriver.ecs.provider.view.EcsServiceDiscoveryProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;

@RestController
public class EcsServiceDiscoveryController {

EcsServiceDiscoveryProvider serviceDiscoveryProvider;

@Autowired
public EcsServiceDiscoveryController(EcsServiceDiscoveryProvider serviceDiscoveryProvider) {
this.serviceDiscoveryProvider = serviceDiscoveryProvider;
}

@RequestMapping(value = {"/ecs/serviceDiscoveryRegistries"})
public Collection<ServiceDiscoveryRegistry> getAllServiceDiscoveryRegistries() {
return serviceDiscoveryProvider.getAllServiceDiscoveryRegistries();
}

}
Expand Up @@ -63,6 +63,8 @@ public class CreateServerGroupDescription extends AbstractECSDescription {
Map<String, String> logOptions;
Map<String, String> dockerLabels;

List<ServiceDiscoveryAssociation> serviceDiscoveryAssociations;

@Override
public String getRegion() {
//CreateServerGroupDescription does not contain a region. Instead it has AvailabilityZones
Expand All @@ -77,4 +79,20 @@ public static class Source {
String asgName;
Boolean useSourceCapacity;
}

@Data
@EqualsAndHashCode(callSuper = false)
public static class ServiceDiscoveryAssociation {
ServiceRegistry registry;
Integer containerPort;
String containerName;
}

@Data
@EqualsAndHashCode(callSuper = false)
public static class ServiceRegistry {
String arn;
String name;
String id;
}
}
Expand Up @@ -157,6 +157,24 @@ protected RegisterTaskDefinitionRequest makeTaskDefinitionRequest(String ecsServ
Collection<PortMapping> portMappings = new LinkedList<>();
portMappings.add(portMapping);

if (description.getServiceDiscoveryAssociations() != null) {
for (CreateServerGroupDescription.ServiceDiscoveryAssociation config : description.getServiceDiscoveryAssociations()) {
if (config.getContainerPort() != null && config.getContainerPort() != 0 && config.getContainerPort() != description.getContainerPort()) {
portMapping = new PortMapping().withProtocol("tcp");
if (AWSVPC_NETWORK_MODE.equals(description.getNetworkMode())) {
portMapping
.withHostPort(config.getContainerPort())
.withContainerPort(config.getContainerPort());
} else {
portMapping
.withHostPort(0)
.withContainerPort(config.getContainerPort());
}
portMappings.add(portMapping);
}
}
}

ContainerDefinition containerDefinition = new ContainerDefinition()
.withName(EcsServerGroupNameResolver.getEcsContainerName(newServerGroupName))
.withEnvironment(containerEnvironment)
Expand Down Expand Up @@ -238,6 +256,25 @@ private Service createService(AmazonECS ecs, TaskDefinition taskDefinition, Stri
desiredCount = sourceService.getDesiredCount();
}

Collection<ServiceRegistry> serviceRegistries = new LinkedList<>();
if (description.getServiceDiscoveryAssociations() != null) {
for (CreateServerGroupDescription.ServiceDiscoveryAssociation config : description.getServiceDiscoveryAssociations()) {
ServiceRegistry registryEntry = new ServiceRegistry().withRegistryArn(config.getRegistry().getArn());

if (config.getContainerPort() != null && config.getContainerPort() != 0) {
registryEntry.setContainerPort(config.getContainerPort());

if (StringUtils.isEmpty(config.getContainerName())) {
registryEntry.setContainerName(EcsServerGroupNameResolver.getEcsContainerName(newServerGroupName));
} else {
registryEntry.setContainerName(config.getContainerName());
}
}

serviceRegistries.add(registryEntry);
}
}

String taskDefinitionArn = taskDefinition.getTaskDefinitionArn();

DeploymentConfiguration deploymentConfiguration = new DeploymentConfiguration()
Expand All @@ -251,6 +288,7 @@ private Service createService(AmazonECS ecs, TaskDefinition taskDefinition, Stri
.withLoadBalancers(loadBalancers)
.withTaskDefinition(taskDefinitionArn)
.withPlacementStrategy(description.getPlacementStrategySequence())
.withServiceRegistries(serviceRegistries)
.withDeploymentConfiguration(deploymentConfiguration);

if (!AWSVPC_NETWORK_MODE.equals(description.getNetworkMode())) {
Expand Down