/*
* Copyright 2013-2017 the original author or authors.
*
* 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 org.springframework.cloud.consul.serviceregistry;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletContext;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.cloud.client.discovery.ManagementServerPortUtils;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.cloud.consul.discovery.ConsulDiscoveryProperties;
import org.springframework.cloud.consul.discovery.HeartbeatProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.ecwid.consul.v1.agent.model.NewService;
/**
* @author Spencer Gibb
*/
public class ConsulAutoRegistration extends ConsulRegistration {
public static final char SEPARATOR = '-';
private final ConsulDiscoveryProperties properties;
private final ApplicationContext context;
private final HeartbeatProperties heartbeatProperties;
public ConsulAutoRegistration(NewService service, ConsulDiscoveryProperties properties, ApplicationContext context, HeartbeatProperties heartbeatProperties) {
super(service);
this.properties = properties;
this.context = context;
this.heartbeatProperties = heartbeatProperties;
}
public void initializePort(int knownPort) {
if (getService().getPort() == null) {
// not set by properties
getService().setPort(knownPort);
}
// we might not have a port until now, so this is the earliest we
// can create a check
setCheck(getService(), this.properties, this.context, this.heartbeatProperties);
}
public ConsulAutoRegistration managementRegistration() {
return managementRegistration(this.properties, this.context, this.heartbeatProperties);
}
public static ConsulAutoRegistration registration(ConsulDiscoveryProperties properties, ApplicationContext context,
ServletContext servletContext, HeartbeatProperties heartbeatProperties) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(context.getEnvironment());
NewService service = new NewService();
String appName = getAppName(properties, propertyResolver);
service.setId(getInstanceId(properties, context));
if(!properties.isPreferAgentAddress()) {
service.setAddress(properties.getHostname());
}
service.setName(normalizeForDns(appName));
service.setTags(createTags(properties, servletContext));
if (properties.getPort() != null) {
service.setPort(properties.getPort());
// we know the port and can set the check
setCheck(service, properties, context, heartbeatProperties);
}
return new ConsulAutoRegistration(service, properties, context, heartbeatProperties);
}
@Deprecated //TODO: do I need this here, or should I just copy what I need back into lifecycle?
public static ConsulAutoRegistration lifecycleRegistration(Integer port, String instanceId, ConsulDiscoveryProperties properties, ApplicationContext context,
ServletContext servletContext, HeartbeatProperties heartbeatProperties) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(context.getEnvironment());
NewService service = new NewService();
String appName = getAppName(properties, propertyResolver);
service.setId(instanceId);
if(!properties.isPreferAgentAddress()) {
service.setAddress(properties.getHostname());
}
service.setName(normalizeForDns(appName));
service.setTags(createTags(properties, servletContext));
// If an alternate external port is specified, register using it instead
if (properties.getPort() != null) {
service.setPort(properties.getPort());
} else {
service.setPort(port);
}
Assert.notNull(service.getPort(), "service.port may not be null");
setCheck(service, properties, context, heartbeatProperties);
return new ConsulAutoRegistration(service, properties, context, heartbeatProperties);
}
public static void setCheck(NewService service, ConsulDiscoveryProperties properties, ApplicationContext context, HeartbeatProperties heartbeatProperties) {
if (properties.isRegisterHealthCheck() && service.getCheck() == null) {
Integer checkPort;
if (shouldRegisterManagement(properties, context)) {
checkPort = getManagementPort(properties, context);
} else {
checkPort = service.getPort();
}
Assert.notNull(checkPort, "checkPort may not be null");
service.setCheck(createCheck(checkPort, heartbeatProperties, properties));
}
}
public static ConsulAutoRegistration managementRegistration(ConsulDiscoveryProperties properties, ApplicationContext context,
HeartbeatProperties heartbeatProperties) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(context.getEnvironment());
NewService management = new NewService();
management.setId(getManagementServiceId(properties, context));
management.setAddress(properties.getHostname());
management.setName(getManagementServiceName(properties, propertyResolver));
management.setPort(getManagementPort(properties, context));
management.setTags(properties.getManagementTags());
if (properties.isRegisterHealthCheck()) {
management.setCheck(createCheck(getManagementPort(properties, context), heartbeatProperties, properties));
}
return new ConsulAutoRegistration(management, properties, context, heartbeatProperties);
}
public static String getInstanceId(ConsulDiscoveryProperties properties, ApplicationContext context) {
if (!StringUtils.hasText(properties.getInstanceId())) {
return normalizeForDns(context.getId());
} else {
return normalizeForDns(properties.getInstanceId());
}
}
public static String normalizeForDns(String s) {
if (s == null || !Character.isLetter(s.charAt(0))
|| !Character.isLetterOrDigit(s.charAt(s.length()-1))) {
throw new IllegalArgumentException("Consul service ids must not be empty, must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen");
}
StringBuilder normalized = new StringBuilder();
Character prev = null;
for (char curr : s.toCharArray()) {
Character toAppend = null;
if (Character.isLetterOrDigit(curr)) {
toAppend = curr;
} else if (prev == null || !(prev == SEPARATOR)) {
toAppend = SEPARATOR;
}
if (toAppend != null) {
normalized.append(toAppend);
prev = toAppend;
}
}
return normalized.toString();
}
public static List<String> createTags(ConsulDiscoveryProperties properties, ServletContext servletContext) {
List<String> tags = new LinkedList<>(properties.getTags());
if(servletContext != null
&& StringUtils.hasText(servletContext.getContextPath())
&& StringUtils.hasText(servletContext.getContextPath().replaceAll("/", ""))) {
tags.add("contextPath=" + servletContext.getContextPath());
}
if (!StringUtils.isEmpty(properties.getInstanceZone())) {
tags.add(properties.getDefaultZoneMetadataName() + "=" + properties.getInstanceZone());
}
if (!StringUtils.isEmpty(properties.getInstanceGroup())) {
tags.add("group=" + properties.getInstanceGroup());
}
return tags;
}
public static NewService.Check createCheck(Integer port, HeartbeatProperties ttlConfig,
ConsulDiscoveryProperties properties) {
NewService.Check check = new NewService.Check();
if (ttlConfig.isEnabled()) {
check.setTtl(ttlConfig.getTtl());
return check;
}
Assert.notNull(port, "createCheck port must not be null");
Assert.isTrue(port > 0, "createCheck port must be greater than 0");
if (properties.getHealthCheckUrl() != null) {
check.setHttp(properties.getHealthCheckUrl());
} else {
check.setHttp(String.format("%s://%s:%s%s", properties.getScheme(),
properties.getHostname(), port,
properties.getHealthCheckPath()));
}
check.setInterval(properties.getHealthCheckInterval());
check.setTimeout(properties.getHealthCheckTimeout());
if (StringUtils.hasText(properties.getHealthCheckCriticalTimeout())) {
check.setDeregisterCriticalServiceAfter(properties.getHealthCheckCriticalTimeout());
}
return check;
}
/**
* @return the app name, currently the spring.application.name property
*/
public static String getAppName(ConsulDiscoveryProperties properties, RelaxedPropertyResolver propertyResolver) {
String appName = properties.getServiceName();
if (!StringUtils.isEmpty(appName)) {
return appName;
}
return propertyResolver.getProperty("spring.application.name", "application");
}
/**
* @return if the management service should be registered with the {@link ServiceRegistry}
*/
public static boolean shouldRegisterManagement(ConsulDiscoveryProperties properties, ApplicationContext context) {
return getManagementPort(properties, context) != null && ManagementServerPortUtils.isDifferent(context);
}
/**
* @return the serviceId of the Management Service
*/
public static String getManagementServiceId(ConsulDiscoveryProperties properties, ApplicationContext context) {
return normalizeForDns(context.getId()) + SEPARATOR + properties.getManagementSuffix();
}
/**
* @return the service name of the Management Service
*/
public static String getManagementServiceName(ConsulDiscoveryProperties properties, RelaxedPropertyResolver propertyResolver) {
return normalizeForDns(getAppName(properties, propertyResolver)) + SEPARATOR + properties.getManagementSuffix();
}
/**
* @return the port of the Management Service
*/
public static Integer getManagementPort(ConsulDiscoveryProperties properties, ApplicationContext context) {
// If an alternate external port is specified, use it instead
if (properties.getManagementPort() != null) {
return properties.getManagementPort();
}
return ManagementServerPortUtils.getPort(context);
}
}