package io.cattle.platform.servicediscovery.api.util;
import io.cattle.platform.allocator.service.AllocationHelper;
import io.cattle.platform.core.addon.InServiceUpgradeStrategy;
import io.cattle.platform.core.addon.InstanceHealthCheck;
import io.cattle.platform.core.constants.AgentConstants;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.ServiceConstants;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.model.Service;
import io.cattle.platform.core.model.Stack;
import io.cattle.platform.core.util.PortSpec;
import io.cattle.platform.core.util.SystemLabels;
import io.cattle.platform.object.meta.ObjectMetaDataManager;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.object.util.DataUtils;
import io.cattle.platform.servicediscovery.api.resource.ServiceDiscoveryConfigItem;
import io.cattle.platform.util.type.CollectionUtils;
import io.github.ibuildthecloud.gdapi.validation.ValidationErrorCodes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
public class ServiceDiscoveryUtil {
public static final List<String> SERVICE_INSTANCE_NAME_DIVIDORS = Arrays.asList("-", "_");
private static final int LB_HEALTH_CHECK_PORT = 42;
public static String getInstanceName(Instance instance) {
if (instance != null && instance.getRemoved() == null) {
return instance.getUuid();
} else {
return null;
}
}
public static String getServiceSuffixFromInstanceName(String instanceName) {
for (String divider : SERVICE_INSTANCE_NAME_DIVIDORS) {
if (!instanceName.contains(divider)) {
continue;
}
String serviceSuffix = instanceName.substring(instanceName.lastIndexOf(divider) + 1);
if (!StringUtils.isEmpty(serviceSuffix) && serviceSuffix.matches("\\d+")) {
return serviceSuffix;
}
}
return "";
}
@SuppressWarnings("unchecked")
public static List<String> getServiceLaunchConfigNames(Service service) {
Map<String, Object> originalData = new HashMap<>();
originalData.putAll(DataUtils.getFields(service));
List<String> launchConfigNames = new ArrayList<>();
// put the primary config in
launchConfigNames.add(ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME);
// put the secondary configs in
Object secondaryLaunchConfigs = originalData
.get(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS);
if (secondaryLaunchConfigs != null) {
for (Map<String, Object> secondaryLaunchConfig : (List<Map<String, Object>>) secondaryLaunchConfigs) {
launchConfigNames.add(String.valueOf(secondaryLaunchConfig.get("name")));
}
}
return launchConfigNames;
}
public static Map<String, Object> getLaunchConfigWithServiceDataAsMap(Service service, String launchConfigName) {
Map<String, Object> data = new HashMap<>();
// 1) get service data
data.putAll(DataUtils.getFields(service));
// 2) remove launchConfig/secondaryConfig data
Object launchConfig = data
.get(ServiceConstants.FIELD_LAUNCH_CONFIG);
if (launchConfig != null) {
data.remove(ServiceConstants.FIELD_LAUNCH_CONFIG);
}
Object secondaryLaunchConfigs = data
.get(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS);
if (secondaryLaunchConfigs != null) {
data.remove(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS);
}
// 3) populate launch config data
data.putAll(getLaunchConfigDataAsMap(service, launchConfigName));
return data;
}
@SuppressWarnings("unchecked")
public static Map<String, Map<Object, Object>> getServiceLaunchConfigsWithNames(Service service) {
Map<String, Object> originalData = new HashMap<>();
originalData.putAll(DataUtils.getFields(service));
Map<String, Map<Object, Object>> launchConfigsWithNames = new HashMap<>();
// put the primary config in
launchConfigsWithNames.put(ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME,
CollectionUtils.toMap(originalData
.get(ServiceConstants.FIELD_LAUNCH_CONFIG)));
// put the secondary configs in
Object secondaryLaunchConfigs = originalData
.get(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS);
if (secondaryLaunchConfigs != null) {
for (Map<String, Object> secondaryLaunchConfig : (List<Map<String, Object>>) secondaryLaunchConfigs) {
launchConfigsWithNames.put(String.valueOf(secondaryLaunchConfig.get("name")),
CollectionUtils.toMap(secondaryLaunchConfig));
}
}
return launchConfigsWithNames;
}
@SuppressWarnings("unchecked")
public static Map<String, String> getMergedServiceLabels(Service service, AllocationHelper allocationHelper) {
List<String> launchConfigNames = getServiceLaunchConfigNames(service);
Map<String, String> labelsStr = new HashMap<>();
for (String currentLaunchConfigName : launchConfigNames) {
Map<String, Object> data = getLaunchConfigDataAsMap(service, currentLaunchConfigName);
Object l = data.get(ServiceDiscoveryConfigItem.LABELS.getCattleName());
if (l != null) {
Map<String, String> labels = (HashMap<String, String>) l;
allocationHelper.mergeLabels(labels, labelsStr);
}
}
return labelsStr;
}
@SuppressWarnings("unchecked")
public static Map<String, String> getLaunchConfigLabels(Service service, String launchConfigName) {
if (launchConfigName == null) {
launchConfigName = ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME;
}
Map<String, Object> data = getLaunchConfigDataAsMap(service, launchConfigName);
Object labels = data.get(InstanceConstants.FIELD_LABELS);
if (labels == null) {
return new HashMap<String, String>();
}
return (Map<String, String>) labels;
}
@SuppressWarnings("unchecked")
public static Map<String, Object> getLaunchConfigDataAsMap(Service service, String launchConfigName) {
if (launchConfigName == null) {
launchConfigName = ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME;
}
Map<String, Object> launchConfigData = new HashMap<>();
if (launchConfigName.equalsIgnoreCase(ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME)) {
launchConfigData = DataAccessor.fields(service)
.withKey(ServiceConstants.FIELD_LAUNCH_CONFIG).withDefault(Collections.EMPTY_MAP)
.as(Map.class);
// if the value is empty, do not export
ArrayList<String> deletedKeys = new ArrayList<>();
for (String key: launchConfigData.keySet()) {
if (launchConfigData.get(key) == null) {
deletedKeys.add(key);
}
}
for (String key: deletedKeys) {
launchConfigData.remove(key);
}
} else {
List<Map<String, Object>> secondaryLaunchConfigs = DataAccessor.fields(service)
.withKey(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS)
.withDefault(Collections.EMPTY_LIST).as(
List.class);
for (Map<String, Object> secondaryLaunchConfig : secondaryLaunchConfigs) {
if (secondaryLaunchConfig.get("name").toString().equalsIgnoreCase(launchConfigName)) {
launchConfigData = secondaryLaunchConfig;
break;
}
}
}
Map<String, Object> data = new HashMap<>();
data.putAll(launchConfigData);
Object labels = data.get(ServiceDiscoveryConfigItem.LABELS.getCattleName());
if (labels != null) {
Map<String, String> labelsMap = new HashMap<String, String>();
labelsMap.putAll((Map<String, String>) labels);
// overwrite with a copy of the map
data.put(ServiceDiscoveryConfigItem.LABELS.getCattleName(), labelsMap);
}
return data;
}
public static Object getLaunchConfigObject(Service service, String launchConfigName, String objectName) {
Map<String, Object> serviceData = ServiceDiscoveryUtil.getLaunchConfigDataAsMap(service, launchConfigName);
return serviceData.get(objectName);
}
public static String generateServiceInstanceName(Stack env, Service service, String launchConfigName,
int finalOrder) {
String configName = launchConfigName == null
|| launchConfigName.equals(ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME) ? ""
: launchConfigName + "-";
String name = String.format("%s-%s-%s%d", env.getName(), service.getName(), configName, finalOrder);
return name;
}
public static boolean isServiceGeneratedName(Stack env, Service service, String instanceName) {
for (String divider : SERVICE_INSTANCE_NAME_DIVIDORS) {
if (instanceName.startsWith(String.format("%s%s%s", env.getName(), divider, service.getName()))) {
return true;
}
}
return false;
}
public static String getGeneratedServiceIndex(Stack env, Service service,
String launchConfigName,
String instanceName) {
if (!isServiceGeneratedName(env, service, instanceName)) {
return null;
}
Integer charAt = instanceName.length()-1;
for (int i = instanceName.length() - 1; i > 0; i--) {
if (instanceName.charAt(i) == '-' || instanceName.charAt(i) == '_') {
break;
}
charAt = i;
}
return instanceName.substring(charAt, instanceName.length());
}
@SuppressWarnings("unchecked")
public static Map<String, Object> buildServiceInstanceLaunchData(Service service, Map<String, Object> deployParams,
String launchConfigName, AllocationHelper allocationHelper) {
Map<String, Object> serviceData = getLaunchConfigDataAsMap(service, launchConfigName);
Map<String, Object> launchConfigItems = new HashMap<>();
// 1. put all parameters retrieved through deployParams
if (deployParams != null) {
launchConfigItems.putAll(deployParams);
}
// 2. Get parameters defined on the service level (merge them with the ones defined in
for (String key : serviceData.keySet()) {
Object dataObj = serviceData.get(key);
if (launchConfigItems.get(key) != null) {
if (dataObj instanceof Map) {
// unfortunately, need to make an except for labels due to the merging aspect of the values
if (key.equalsIgnoreCase(InstanceConstants.FIELD_LABELS)) {
allocationHelper.normalizeLabels(
service.getStackId(),
(Map<String, String>) launchConfigItems.get(key),
(Map<String, String>) dataObj);
allocationHelper.mergeLabels((Map<String, String>) launchConfigItems.get(key),
(Map<String, String>) dataObj);
} else {
((Map<Object, Object>) dataObj).putAll((Map<Object, Object>) launchConfigItems.get(key));
}
} else if (dataObj instanceof List) {
for (Object existing : (List<Object>) launchConfigItems.get(key)) {
if (!((List<Object>) dataObj).contains(existing)) {
((List<Object>) dataObj).add(existing);
}
}
}
}
if (dataObj != null) {
launchConfigItems.put(key, dataObj);
}
}
// 3. add extra parameters
launchConfigItems.put("accountId", service.getAccountId());
if (!launchConfigItems.containsKey(ObjectMetaDataManager.KIND_FIELD)) {
launchConfigItems.put(ObjectMetaDataManager.KIND_FIELD, InstanceConstants.KIND_CONTAINER);
}
return launchConfigItems;
}
public static boolean isNoopService(Service service) {
Object imageUUID = ServiceDiscoveryUtil.getLaunchConfigDataAsMap(service,
ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME).get(
InstanceConstants.FIELD_IMAGE_UUID);
return (service.getSelectorContainer() != null
&& (imageUUID == null || imageUUID.toString().toLowerCase()
.contains(ServiceConstants.IMAGE_NONE))) || isNoopLBService(service);
}
public static boolean isNoopLBService(Service service) {
Object imageUUID = ServiceDiscoveryUtil.getLaunchConfigDataAsMap(service,
ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME).get(
InstanceConstants.FIELD_IMAGE_UUID);
return service.getKind().equalsIgnoreCase(ServiceConstants.KIND_LOAD_BALANCER_SERVICE)
&& imageUUID != null && imageUUID.toString().toLowerCase()
.contains(ServiceConstants.IMAGE_NONE);
}
public static void upgradeServiceConfigs(Service service, InServiceUpgradeStrategy strategy, boolean rollback) {
updatePrimaryLaunchConfig(strategy, service, rollback);
updateSecondaryLaunchConfigs(strategy, service, rollback);
}
@SuppressWarnings("unchecked")
protected static void updateSecondaryLaunchConfigs(InServiceUpgradeStrategy strategy, Service service,
boolean rollback) {
Object newLaunchConfigs = null;
if (rollback) {
newLaunchConfigs = strategy.getPreviousSecondaryLaunchConfigs();
} else {
newLaunchConfigs = strategy.getSecondaryLaunchConfigs();
Map<String, Map<String, Object>> newLaunchConfigNames = new HashMap<>();
if (newLaunchConfigs != null) {
for (Map<String, Object> newLaunchConfig : (List<Map<String, Object>>) newLaunchConfigs) {
newLaunchConfigNames.put(newLaunchConfig.get("name").toString(),
newLaunchConfig);
}
Object oldLaunchConfigs = strategy.getPreviousSecondaryLaunchConfigs();
for (Map<String, Object> oldLaunchConfig : (List<Map<String, Object>>)oldLaunchConfigs) {
Map<String, Object> newLaunchConfig = newLaunchConfigNames
.get(oldLaunchConfig.get("name"));
if (newLaunchConfig != null) {
preserveOldRandomPorts(service, newLaunchConfig, oldLaunchConfig);
}
}
}
}
DataAccessor.fields(service).withKey(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS)
.set(newLaunchConfigs);
}
@SuppressWarnings("unchecked")
protected static void updatePrimaryLaunchConfig(InServiceUpgradeStrategy strategy, Service service, boolean rollback) {
Map<String, Object> newLaunchConfig = null;
if (rollback) {
newLaunchConfig = (Map<String, Object>) strategy.getPreviousLaunchConfig();
} else {
newLaunchConfig = (Map<String, Object>) strategy.getLaunchConfig();
Map<String, Object> oldLaunchConfig = (Map<String, Object>) strategy.getPreviousLaunchConfig();
preserveOldRandomPorts(service, newLaunchConfig, oldLaunchConfig);
}
DataAccessor.fields(service).withKey(ServiceConstants.FIELD_LAUNCH_CONFIG)
.set(newLaunchConfig);
}
protected static void preserveOldRandomPorts(Service service, Map<String, Object> newLaunchConfig, Map<String, Object> oldLaunchConfig) {
Map<Integer, PortSpec> oldPortMap = getServicePortsMap(service, oldLaunchConfig);
Map<Integer, PortSpec> newPortMap = getServicePortsMap(service, newLaunchConfig);
boolean changedNewPorts = false;
for(Integer privatePort : newPortMap.keySet()) {
if(newPortMap.get(privatePort).getPublicPort() == null) {
if (oldPortMap.containsKey(privatePort)) {
newPortMap.get(privatePort).setPublicPort(oldPortMap.get(privatePort).getPublicPort());
changedNewPorts = true;
}
}
}
if(changedNewPorts) {
List<String> newPorts = new ArrayList<>();
for (Map.Entry<Integer, PortSpec> entry : newPortMap.entrySet()) {
newPorts.add(entry.getValue().toSpec());
}
if (!newPorts.isEmpty()) {
newLaunchConfig.put(InstanceConstants.FIELD_PORTS, newPorts);
}
}
}
@SuppressWarnings("unchecked")
protected static Map<Integer, PortSpec> getServicePortsMap(Service service, Map<String, Object> launchConfigData) {
if (launchConfigData.get(InstanceConstants.FIELD_PORTS) == null) {
return new LinkedHashMap<Integer, PortSpec>();
}
List<String> specs = (List<String>) launchConfigData.get(InstanceConstants.FIELD_PORTS);
Map<Integer, PortSpec> portMap = new LinkedHashMap<Integer, PortSpec>();
for (String spec : specs) {
PortSpec portSpec = new PortSpec(spec);
portMap.put(new Integer(portSpec.getPrivatePort()), portSpec);
}
return portMap;
}
@SuppressWarnings("unchecked")
public static void injectBalancerLabelsAndHealthcheck(Map<Object, Object> launchConfig) {
Map<String, String> labels = new HashMap<>();
// set labels
Object labelsObj = launchConfig.get(InstanceConstants.FIELD_LABELS);
if (labelsObj != null) {
labels = (Map<String, String>) labelsObj;
}
if (!labels.containsKey(SystemLabels.LABEL_AGENT_ROLE)) {
labels.put(SystemLabels.LABEL_AGENT_ROLE, AgentConstants.ENVIRONMENT_ADMIN_ROLE);
labels.put(SystemLabels.LABEL_AGENT_CREATE, "true");
}
launchConfig.put(InstanceConstants.FIELD_LABELS, labels);
// set health check
if (launchConfig.get(InstanceConstants.FIELD_HEALTH_CHECK) == null) {
Integer healthCheckPort = LB_HEALTH_CHECK_PORT;
InstanceHealthCheck healthCheck = new InstanceHealthCheck();
healthCheck.setPort(healthCheckPort);
healthCheck.setInterval(2000);
healthCheck.setHealthyThreshold(2);
healthCheck.setUnhealthyThreshold(3);
healthCheck.setResponseTimeout(2000);
healthCheck.setInitializingTimeout(60000);
healthCheck.setReinitializingTimeout(60000);
launchConfig.put(InstanceConstants.FIELD_HEALTH_CHECK, healthCheck);
}
}
@SuppressWarnings("unchecked")
public static void validateScaleSwitch(Object newLaunchConfig, Object currentLaunchConfig) {
if (isGlobalService((Map<Object, Object>) currentLaunchConfig) != isGlobalService((Map<Object, Object>) newLaunchConfig)) {
ValidationErrorCodes.throwValidationError(ValidationErrorCodes.INVALID_OPTION,
"Switching from global scale to fixed (and vice versa)");
}
}
@SuppressWarnings("unchecked")
protected static boolean isGlobalService(Map<Object, Object> launchConfig) {
// set labels
Object labelsObj = launchConfig.get(InstanceConstants.FIELD_LABELS);
if (labelsObj == null) {
return false;
}
Map<String, String> labels = (Map<String, String>) labelsObj;
String globalService = labels.get(ServiceConstants.LABEL_SERVICE_GLOBAL);
return Boolean.valueOf(globalService);
}
}