package io.cattle.platform.servicediscovery.service.impl; import static io.cattle.platform.core.model.tables.AccountLinkTable.*; import static io.cattle.platform.core.model.tables.ServiceIndexTable.*; import static io.cattle.platform.core.model.tables.ServiceTable.*; import static io.cattle.platform.core.model.tables.StackTable.*; import static io.cattle.platform.core.model.tables.SubnetTable.*; import io.cattle.platform.allocator.service.AllocationHelper; import io.cattle.platform.configitem.events.ConfigUpdate; import io.cattle.platform.configitem.model.Client; import io.cattle.platform.configitem.request.ConfigUpdateRequest; import io.cattle.platform.configitem.version.ConfigItemStatusManager; import io.cattle.platform.core.addon.LbConfig; import io.cattle.platform.core.addon.PortRule; import io.cattle.platform.core.addon.PublicEndpoint; import io.cattle.platform.core.addon.ScalePolicy; import io.cattle.platform.core.addon.ServiceLink; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.HealthcheckConstants; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.core.constants.IpAddressConstants; import io.cattle.platform.core.constants.ServiceConstants; import io.cattle.platform.core.constants.SubnetConstants; import io.cattle.platform.core.dao.InstanceDao; import io.cattle.platform.core.dao.NetworkDao; import io.cattle.platform.core.model.Account; import io.cattle.platform.core.model.AccountLink; import io.cattle.platform.core.model.Host; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.Network; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.ServiceConsumeMap; import io.cattle.platform.core.model.ServiceIndex; import io.cattle.platform.core.model.Stack; import io.cattle.platform.core.model.Subnet; import io.cattle.platform.core.util.PortSpec; import io.cattle.platform.core.util.SystemLabels; import io.cattle.platform.deferred.util.DeferredUtils; import io.cattle.platform.eventing.EventService; import io.cattle.platform.iaas.api.filter.apikey.ApiKeyFilter; import io.cattle.platform.json.JsonMapper; import io.cattle.platform.lock.LockManager; import io.cattle.platform.network.IPAssignment; import io.cattle.platform.network.NetworkService; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.process.ObjectProcessManager; import io.cattle.platform.object.process.StandardProcess; import io.cattle.platform.object.resource.ResourceMonitor; import io.cattle.platform.object.resource.ResourcePredicate; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.object.util.ObjectUtils; import io.cattle.platform.resource.pool.PooledResource; import io.cattle.platform.resource.pool.PooledResourceOptions; import io.cattle.platform.resource.pool.ResourcePoolManager; import io.cattle.platform.resource.pool.util.ResourcePoolConstants; import io.cattle.platform.servicediscovery.api.dao.ServiceConsumeMapDao; import io.cattle.platform.servicediscovery.api.dao.ServiceExposeMapDao; import io.cattle.platform.servicediscovery.api.util.ServiceDiscoveryUtil; import io.cattle.platform.servicediscovery.api.util.selector.SelectorUtils; import io.cattle.platform.servicediscovery.service.ServiceDiscoveryService; import io.cattle.platform.util.exception.ResourceExhaustionException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; public class ServiceDiscoveryServiceImpl implements ServiceDiscoveryService { private static final String HOST_ENDPOINTS_UPDATE = "host-endpoints-update"; private static final String SERVICE_ENDPOINTS_UPDATE = "service-endpoints-update"; @Inject ServiceConsumeMapDao consumeMapDao; @Inject ObjectManager objectManager; @Inject NetworkDao ntwkDao; @Inject ObjectProcessManager objectProcessManager; @Inject ServiceExposeMapDao exposeMapDao; @Inject JsonMapper jsonMapper; @Inject ResourcePoolManager poolManager; @Inject ResourceMonitor resourceMonitor; @Inject LockManager lockManager; @Inject AllocationHelper allocationHelper; @Inject EventService eventService; @Inject InstanceDao instanceDao; @Inject ConfigItemStatusManager itemManager; @Inject NetworkService networkService; @Override public List<Integer> getServiceInstanceUsedSuffixes(Service service, String launchConfigName) { Stack env = objectManager.findOne(Stack.class, STACK.ID, service.getStackId()); // get all existing instances to check if the name is in use by the instance of the same service List<Integer> usedSuffixes = new ArrayList<>(); List<? extends Instance> serviceInstances = exposeMapDao.listServiceManagedInstances(service, launchConfigName); for (Instance instance : serviceInstances) { if (ServiceDiscoveryUtil.isServiceGeneratedName(env, service, instance.getName())) { // legacy code - to support old data where service suffix wasn't set usedSuffixes.add(Integer.valueOf(ServiceDiscoveryUtil.getServiceSuffixFromInstanceName(instance .getName()))); } } return usedSuffixes; } @Override public void removeServiceMaps(Service service) { // 1. remove all maps to the services consumed by service specified for (ServiceConsumeMap map : consumeMapDao.findConsumedMapsToRemove(service.getId())) { objectProcessManager.scheduleProcessInstance(ServiceConstants.PROCESS_SERVICE_CONSUME_MAP_REMOVE, map, null); } // 2. remove all maps to the services consuming service specified for (ServiceConsumeMap map : consumeMapDao.findConsumingMapsToRemove(service.getId())) { objectProcessManager.scheduleProcessInstance(ServiceConstants.PROCESS_SERVICE_CONSUME_MAP_REMOVE, map, null); } } @Override public boolean isActiveService(Service service) { return (getServiceActiveStates().contains(service.getState())); } @Override public List<String> getServiceActiveStates() { return Arrays.asList(CommonStatesConstants.ACTIVATING, CommonStatesConstants.ACTIVE, CommonStatesConstants.UPDATING_ACTIVE, ServiceConstants.STATE_UPGRADING, ServiceConstants.STATE_ROLLINGBACK, ServiceConstants.STATE_CANCELING_UPGRADE, ServiceConstants.STATE_CANCELED_UPGRADE, ServiceConstants.STATE_FINISHING_UPGRADE, ServiceConstants.STATE_UPGRADED, ServiceConstants.STATE_RESTARTING); } @Override public void cloneConsumingServices(Service fromService, Service toService) { List<ServiceLink> linksToCreate = new ArrayList<>(); for (ServiceConsumeMap map : consumeMapDao.findConsumingServices(fromService.getId())) { ServiceLink link = new ServiceLink(toService.getId(), map.getName()); link.setConsumingServiceId(map.getServiceId()); linksToCreate.add(link); } consumeMapDao.createServiceLinks(linksToCreate); } protected String allocateVip(Service service) { if (ServiceConstants.SERVICE_LIKE.contains(service.getKind())) { Subnet vipSubnet = getServiceVipSubnet(service); String requestedVip = service.getVip(); return allocateIpForService(service, vipSubnet, requestedVip); } return null; } @Override public String allocateIpForService(Object owner, Subnet subnet, String requestedIp) { PooledResourceOptions options = new PooledResourceOptions(); if (requestedIp != null) { options.setRequestedItem(requestedIp); } PooledResource resource = poolManager.allocateOneResource(subnet, owner, options); if (resource != null) { return resource.getName(); } return null; } protected Subnet getServiceVipSubnet(final Service service) { Subnet vipSubnet = DeferredUtils.nest(new Callable<Subnet>() { @Override public Subnet call() throws Exception { return ntwkDao.addVIPSubnet(service.getAccountId()); } }); vipSubnet = waitForSubnetCreation(vipSubnet); return vipSubnet; } public Subnet waitForSubnetCreation(Subnet subnet) { // wait for subnet to become active so the ip range is populated subnet = resourceMonitor.waitFor(subnet, new ResourcePredicate<Subnet>() { @Override public boolean evaluate(Subnet obj) { return CommonStatesConstants.ACTIVE.equals(obj.getState()); } @Override public String getMessage() { return "active state"; } }); return subnet; } @Override public void setVIP(Service service) { if (!(DataAccessor.fieldBool(service, ServiceConstants.FIELD_SET_VIP) || service.getVip() != null)) { return; } String vip = allocateVip(service); if (vip != null) { service.setVip(vip); objectManager.persist(service); } } @SuppressWarnings("unchecked") protected List<PortSpec> getLaunchConfigPorts(Map<String, Object> launchConfigData) { if (launchConfigData.get(InstanceConstants.FIELD_PORTS) == null) { return new ArrayList<>(); } List<String> specs = (List<String>) launchConfigData.get(InstanceConstants.FIELD_PORTS); List<PortSpec> ports = new ArrayList<>(); for (String spec : specs) { ports.add(new PortSpec(spec)); } return ports; } @Override @SuppressWarnings("unchecked") public void setPorts(Service service) { boolean allocatePorts = !service.getKind().equalsIgnoreCase(ServiceConstants.KIND_LOAD_BALANCER_SERVICE); Account env = objectManager.loadResource(Account.class, service.getAccountId()); List<PooledResource> allocatedPorts = new ArrayList<>(); if (allocatePorts) { allocatedPorts = allocatePorts(env, service); } // update primary launchConfig Map<String, Object> launchConfig = DataAccessor.fields(service) .withKey(ServiceConstants.FIELD_LAUNCH_CONFIG).withDefault(Collections.EMPTY_MAP) .as(Map.class); setRandomPublicPorts(env, service, launchConfig, allocatedPorts, allocatePorts); // update secondary launch configs List<Object> secondaryLaunchConfigs = DataAccessor.fields(service) .withKey(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS) .withDefault(Collections.EMPTY_LIST).as( List.class); for (Object secondaryLaunchConfig : secondaryLaunchConfigs) { setRandomPublicPorts(env, service, (Map<String, Object>) secondaryLaunchConfig, allocatedPorts, allocatePorts); } DataAccessor.fields(service).withKey(ServiceConstants.FIELD_LAUNCH_CONFIG).set(launchConfig); DataAccessor.fields(service).withKey(ServiceConstants.FIELD_SECONDARY_LAUNCH_CONFIGS) .set(secondaryLaunchConfigs); objectManager.persist(service); } @SuppressWarnings("unchecked") protected List<PooledResource> allocatePorts(Account env, Service service) { int toAllocate = 0; for (String launchConfigName : ServiceDiscoveryUtil.getServiceLaunchConfigNames(service)) { Object ports = ServiceDiscoveryUtil.getLaunchConfigObject(service, launchConfigName, InstanceConstants.FIELD_PORTS); if (ports != null) { for (String port : (List<String>) ports) { if (new PortSpec(port).getPublicPort() == null) { toAllocate++; } } } } List<PooledResource> resource = null; PooledResourceOptions options = new PooledResourceOptions().withCount(toAllocate).withQualifier(ResourcePoolConstants.ENVIRONMENT_PORT); if (toAllocate > 0) { resource = poolManager.allocateResource(env, service, options); } if (resource == null) { resource = new ArrayList<>(); } if (resource.size() < toAllocate) { poolManager.releaseResource(env, service, options); throw new ResourceExhaustionException( String.format("Not enough environment ports to create service. Needed: %d, available: %d", toAllocate, resource.size()), service); } return resource; } protected void setRandomPublicPorts(Account env, Service service, Map<String, Object> launchConfigData, List<PooledResource> allocatedPorts, boolean allocatePorts) { List<PortSpec> ports = getLaunchConfigPorts(launchConfigData); List<String> newPorts = new ArrayList<>(); List<PortSpec> toAllocate = new ArrayList<>(); for (PortSpec port : ports) { if (port.getPublicPort() == null) { if (!allocatePorts) { if (port.getPublicPort() == null) { port.setPublicPort(port.getPrivatePort()); } newPorts.add(port.toSpec()); } else { toAllocate.add(port); } } else { newPorts.add(port.toSpec()); } } for (PortSpec port : toAllocate) { if (!allocatedPorts.isEmpty()) { port.setPublicPort(new Integer(allocatedPorts.get(0).getName())); allocatedPorts.remove(0); } newPorts.add(port.toSpec()); } if (!newPorts.isEmpty()) { launchConfigData.put(InstanceConstants.FIELD_PORTS, newPorts); } } @Override public void releasePorts(Service service) { Account account = objectManager.loadResource(Account.class, service.getAccountId()); poolManager.releaseResource(account, service, new PooledResourceOptions().withQualifier( ResourcePoolConstants.ENVIRONMENT_PORT)); } @Override public void releaseVip(Service service) { String vip = service.getVip(); if (vip == null) { return; } List<Subnet> subnets = objectManager.find(Subnet.class, SUBNET.ACCOUNT_ID, service.getAccountId(), SUBNET.KIND, SubnetConstants.KIND_VIP_SUBNET); if (subnets.isEmpty()) { return; } Subnet subnet = subnets.get(0); poolManager.releaseResource(subnet, service); } @Override public void addServiceLink(final Service service, final ServiceLink serviceLink) { DeferredUtils.nest(new Runnable() { @Override public void run() { consumeMapDao.createServiceLink(service, serviceLink); } }); } @Override public void removeServiceLink(final Service service, final ServiceLink serviceLink) { DeferredUtils.nest(new Runnable() { @Override public void run() { ServiceConsumeMap map = consumeMapDao.findMapToRemove(service.getId(), serviceLink.getServiceId()); if (map != null) { objectProcessManager.scheduleProcessInstanceAsync( ServiceConstants.PROCESS_SERVICE_CONSUME_MAP_REMOVE, map, null); } } }); } @Override public boolean isSelectorLinkMatch(String selector, Service targetService) { if (StringUtils.isBlank(selector)) { return false; } Map<String, String> serviceLabels = ServiceDiscoveryUtil.getLaunchConfigLabels(targetService, ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME); if (serviceLabels.isEmpty()) { return false; } return SelectorUtils.isSelectorMatch(selector, serviceLabels); } @SuppressWarnings("unchecked") @Override public boolean isSelectorContainerMatch(String selector, Instance instance) { if (StringUtils.isBlank(selector)) { return false; } Map<String, String> labels = DataAccessor.fields(instance).withKey(InstanceConstants.FIELD_LABELS).as(Map.class); if (labels == null || labels.isEmpty()) { return false; } Map<String, String> instanceLabels = new HashMap<>(); for (Map.Entry<String, String> label : labels.entrySet()) { instanceLabels.put(label.getKey(), label.getValue()); } return SelectorUtils.isSelectorMatch(selector, instanceLabels); } @Override public boolean isGlobalService(Service service) { Map<String, String> serviceLabels = ServiceDiscoveryUtil.getMergedServiceLabels(service, allocationHelper); String globalService = serviceLabels.get(ServiceConstants.LABEL_SERVICE_GLOBAL); return Boolean.valueOf(globalService); } protected void updateObjectEndPoints(final Object object, final String resourceType, final Long resourceId, long accountId, List<PublicEndpoint> newData) { // have to reload the object to get the latest update for publicEndpoint // if don't reload, its possible that n concurrent updates would lack information. // update would be performed on the original object Object reloaded = objectManager.reload(object); List<PublicEndpoint> oldData = new ArrayList<>(); oldData.addAll(DataAccessor.fields(reloaded) .withKey(ServiceConstants.FIELD_PUBLIC_ENDPOINTS) .withDefault(Collections.EMPTY_LIST) .asList(jsonMapper, PublicEndpoint.class)); Set<String> newPortToIp = new HashSet<>(); Set<String> oldPortToIp = new HashSet<>(); for (PublicEndpoint newD : newData) { newPortToIp.add(new StringBuilder().append(newD.getPort()).append("_").append(newD.getIpAddress()) .toString()); } for (PublicEndpoint oldD : oldData) { oldPortToIp.add(new StringBuilder().append(oldD.getPort()).append("_").append(oldD.getIpAddress()) .toString()); } if (oldPortToIp.contains(newPortToIp) && newPortToIp.contains(oldPortToIp)) { return; } objectManager.reload(object); objectManager.setFields(object, ServiceConstants.FIELD_PUBLIC_ENDPOINTS, newData); publishEvent(object); } protected void reconcileHostEndpointsImpl(final Host host) { final List<PublicEndpoint> newData = instanceDao.getPublicEndpoints(host.getAccountId(), null, host.getId()); if (host != null && host.getRemoved() == null) { updateObjectEndPoints(host, host.getKind(), host.getId(), host.getAccountId(), newData); } } protected void reconcileServiceEndpointsImpl(final Service service) { final List<PublicEndpoint> newData = instanceDao.getPublicEndpoints(service.getAccountId(), service.getId(), null); if (service != null && service.getRemoved() == null && !ServiceDiscoveryUtil.isNoopLBService(service)) { updateObjectEndPoints(service, service.getKind(), service.getId(), service.getAccountId(), newData); } } @Override public void setToken(Service service) { String token = ApiKeyFilter.generateKeys()[1]; DataAccessor.fields(service).withKey(ServiceConstants.FIELD_TOKEN).set(token); objectManager.persist(service); } @Override public void removeServiceIndexes(Service service) { for (ServiceIndex serviceIndex : objectManager.find(ServiceIndex.class, SERVICE_INDEX.SERVICE_ID, service.getId(), SERVICE_INDEX.REMOVED, null)) { objectProcessManager.scheduleStandardProcessAsync(StandardProcess.REMOVE, serviceIndex, null); } } @Override public void allocateIpToServiceIndex(Service service, ServiceIndex serviceIndex, String requestedIp) { if (StringUtils.isEmpty(serviceIndex.getAddress())) { String ntwkMode = networkService.getNetworkMode(DataAccessor .fieldMap(service, ServiceConstants.FIELD_LAUNCH_CONFIG)); if (ntwkMode == null) { return; } Network ntwk = networkService.resolveNetwork(serviceIndex.getAccountId(), ntwkMode.toString()); if (networkService.shouldAssignIpAddress(ntwk)) { IPAssignment assignment = networkService.assignIpAddress(ntwk, serviceIndex, requestedIp); if (assignment != null) { setServiceIndexIp(serviceIndex, assignment.getIpAddress()); } } } } @Override public void setServiceIndexIp(ServiceIndex serviceIndex, String ipAddress) { objectManager.setFields(serviceIndex, IpAddressConstants.FIELD_ADDRESS, ipAddress); } @Override public void releaseIpFromServiceIndex(Service service, ServiceIndex serviceIndex) { if (!StringUtils.isEmpty(serviceIndex.getAddress())) { String ntwkMode = networkService.getNetworkMode(DataAccessor .fieldMap(service, ServiceConstants.FIELD_LAUNCH_CONFIG)); if (ntwkMode == null) { return; } Network ntwk = networkService.resolveNetwork(serviceIndex.getAccountId(), ntwkMode); networkService.releaseIpAddress(ntwk, serviceIndex); } } @Override public void updateHealthState(final Stack stack) { if (stack == null) { return; } List<Service> services = objectManager.find(Service.class, SERVICE.STACK_ID, stack.getId(), SERVICE.REMOVED, null); setServiceHealthState(services); setStackHealthState(stack); setEnvironmentHealthState(objectManager.loadResource(Account.class, stack.getAccountId())); } protected void setStackHealthState(final Stack stack) { String newHealthState = calculateStackHealthState(stack); String currentHealthState = objectManager.reload(stack).getHealthState(); if (!newHealthState.equalsIgnoreCase(currentHealthState)) { Map<String, Object> fields = new HashMap<>(); fields.put(ServiceConstants.FIELD_HEALTH_STATE, newHealthState); objectManager.setFields(stack, fields); publishEvent(stack); } } protected void setEnvironmentHealthState(final Account env) { if (env == null) { return; } String newHealthState = calculateEnvironmentHealthState(env); String currentHealthState = objectManager.reload(env).getHealthState(); if (!newHealthState.equalsIgnoreCase(currentHealthState)) { Map<String, Object> fields = new HashMap<>(); fields.put(ServiceConstants.FIELD_HEALTH_STATE, newHealthState); objectManager.setFields(env, fields); publishEvent(env); } } protected String calculateStackHealthState(final Stack stack) { List<Service> services = objectManager.find(Service.class, SERVICE.STACK_ID, stack.getId(), SERVICE.REMOVED, null); List<HealthChecker> hcs = new ArrayList<>(); for (Service svc : services) { hcs.add(new ServiceHealthCheck(svc)); } return calculateHealthState(hcs); } protected String calculateEnvironmentHealthState(final Account env) { List<Stack> stacks = objectManager.find(Stack.class, STACK.ACCOUNT_ID, env.getId(), STACK.REMOVED, null); List<HealthChecker> hcs = new ArrayList<>(); for (Stack stack : stacks) { hcs.add(new StackHealthCheck(stack)); } return calculateHealthState(hcs); } private String calculateHealthState(List<? extends HealthChecker> hcs) { int init = 0; int healthy = 0; int expectedCount = 0; int startedOnce = 0; List<String> healthyStates = Arrays.asList( HealthcheckConstants.SERVICE_HEALTH_STATE_STARTED_ONCE.toLowerCase(), HealthcheckConstants.HEALTH_STATE_HEALTHY.toLowerCase()); List<String> ignoreStates = Arrays.asList(CommonStatesConstants.REMOVING, CommonStatesConstants.REMOVED, CommonStatesConstants.PURGED, CommonStatesConstants.PURGING); for (HealthChecker hc : hcs) { String sHS = hc.getHealthState() == null ? HealthcheckConstants.HEALTH_STATE_HEALTHY : hc .getHealthState().toLowerCase(); if (ignoreStates.contains(hc.getState())) { continue; } expectedCount++; if (hc.isActive() && healthyStates.contains(sHS)) { if (sHS.equalsIgnoreCase( HealthcheckConstants.SERVICE_HEALTH_STATE_STARTED_ONCE.toLowerCase())) { startedOnce++; } healthy++; } else if (sHS.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_INITIALIZING)) { init++; } } String finalHealthState = HealthcheckConstants.HEALTH_STATE_UNHEALTHY; if (expectedCount == 0) { finalHealthState = HealthcheckConstants.HEALTH_STATE_HEALTHY; } else if (startedOnce >= expectedCount) { finalHealthState = HealthcheckConstants.SERVICE_HEALTH_STATE_STARTED_ONCE; } else if (healthy >= expectedCount) { finalHealthState = HealthcheckConstants.HEALTH_STATE_HEALTHY; } else if (init > 0) { finalHealthState = HealthcheckConstants.HEALTH_STATE_INITIALIZING; } else if (healthy > 0) { finalHealthState = HealthcheckConstants.SERVICE_HEALTH_STATE_DEGRADED; } return finalHealthState; } protected interface HealthChecker{ String getHealthState(); boolean isActive(); String getState(); } protected class StackHealthCheck implements HealthChecker { Stack stack; protected StackHealthCheck(Stack stack) { this.stack = stack; } @Override public String getHealthState() { return stack.getHealthState(); } @Override public boolean isActive() { List<String> activeStates = Arrays.asList(CommonStatesConstants.ACTIVATING, CommonStatesConstants.ACTIVE, CommonStatesConstants.UPDATING_ACTIVE); return activeStates.contains(stack.getState()); } @Override public String getState() { return stack.getState(); } } protected class ServiceHealthCheck implements HealthChecker { Service svc; protected ServiceHealthCheck(Service svc) { this.svc = svc; } @Override public String getHealthState() { return svc.getHealthState(); } @Override public boolean isActive() { return isActiveService(svc); } @Override public String getState() { return svc.getState(); } } protected void setServiceHealthState(final List<? extends Service> services) { for (Service service : services) { String newHealthState = calculateServiceHealthState(service); String currentHealthState = objectManager.reload(service).getHealthState(); if (!newHealthState.equalsIgnoreCase(currentHealthState)) { Map<String, Object> fields = new HashMap<>(); fields.put(ServiceConstants.FIELD_HEALTH_STATE, newHealthState); objectManager.setFields(service, fields); publishEvent(service); } } } protected String calculateServiceHealthState(Service service) { String serviceHealthState = null; List<String> supportedKinds = Arrays.asList( ServiceConstants.KIND_SERVICE.toLowerCase(), ServiceConstants.KIND_LOAD_BALANCER_SERVICE.toLowerCase()); if (!supportedKinds.contains(service.getKind().toLowerCase())) { serviceHealthState = HealthcheckConstants.HEALTH_STATE_HEALTHY; } else { List<? extends Instance> serviceInstances = exposeMapDao.listServiceManagedInstances(service); List<String> healthyStates = Arrays.asList(HealthcheckConstants.HEALTH_STATE_HEALTHY, HealthcheckConstants.HEALTH_STATE_UPDATING_HEALTHY); List<String> initStates = Arrays.asList(HealthcheckConstants.HEALTH_STATE_INITIALIZING, HealthcheckConstants.HEALTH_STATE_REINITIALIZING); Integer scale = DataAccessor.fieldInteger(service, ServiceConstants.FIELD_SCALE); if (scale == null || ServiceDiscoveryUtil.isNoopService(service)) { scale = 0; } ScalePolicy policy = DataAccessor.field(service, ServiceConstants.FIELD_SCALE_POLICY, jsonMapper, ScalePolicy.class); if (policy != null) { scale = policy.getMin(); } List<String> lcs = ServiceDiscoveryUtil.getServiceLaunchConfigNames(service); Integer expectedScale = scale * lcs.size(); boolean isGlobal = isGlobalService(service); int healthyCount = 0; int initCount = 0; int instanceCount = serviceInstances.size(); int startedOnce = 0; List<String> runningStates = Arrays.asList(InstanceConstants.STATE_RUNNING); for (Instance instance : serviceInstances) { String iHS = instance.getHealthState() == null ? HealthcheckConstants.HEALTH_STATE_HEALTHY : instance .getHealthState().toLowerCase(); if (runningStates.contains(instance.getState().toLowerCase())) { if (healthyStates.contains(iHS)) { healthyCount++; } else if (initStates.contains(iHS)) { initCount++; } } if (isStartOnce(instance)) { startedOnce++; } } if (startedOnce > 0 && ((isGlobal && startedOnce >= instanceCount) || (!isGlobal && startedOnce >= expectedScale))) { return HealthcheckConstants.SERVICE_HEALTH_STATE_STARTED_ONCE; } healthyCount = healthyCount + startedOnce; if ((isGlobal && healthyCount >= instanceCount && instanceCount > 0) || (!isGlobal && healthyCount >= expectedScale)) { return HealthcheckConstants.HEALTH_STATE_HEALTHY; } else if (initCount > 0) { serviceHealthState = HealthcheckConstants.HEALTH_STATE_INITIALIZING; } else if (healthyCount > 0) { serviceHealthState = HealthcheckConstants.SERVICE_HEALTH_STATE_DEGRADED; } else { serviceHealthState = HealthcheckConstants.HEALTH_STATE_UNHEALTHY; } } return serviceHealthState; } protected boolean isStartOnce(Instance instance) { Map<String, Object> labels = DataAccessor .fieldMap(instance, InstanceConstants.FIELD_LABELS); boolean startOnce = false; List<String> stoppedStates = Arrays.asList(InstanceConstants.STATE_STOPPED, InstanceConstants.STATE_STOPPING); if (labels.containsKey(SystemLabels.LABEL_SERVICE_CONTAINER_START_ONCE)) { startOnce = Boolean.valueOf(((String) labels .get(SystemLabels.LABEL_SERVICE_CONTAINER_START_ONCE))) && instance.getStartCount() != null && instance.getStartCount() >= 1L && stoppedStates.contains(instance.getState()); } return startOnce; } protected void publishEvent(Object obj) { ObjectUtils.publishChanged(eventService, objectManager, obj); } @Override public boolean isScalePolicyService(Service service) { return DataAccessor.field(service, ServiceConstants.FIELD_SCALE_POLICY, jsonMapper, ScalePolicy.class) != null; } @Override public void serviceUpdate(ConfigUpdate update) { if (update.getResourceId() == null) { return; } final Client client = new Client(Service.class, new Long(update.getResourceId())); itemManager.runUpdateForEvent(SERVICE_ENDPOINTS_UPDATE, update, client, new Runnable() { @Override public void run() { Service service = objectManager.loadResource(Service.class, client.getResourceId()); if (service != null && service.getRemoved() == null) { reconcileServiceEndpointsImpl(service); } } }); } @Override public void hostEndpointsUpdate(ConfigUpdate update) { if (update.getResourceId() == null) { return; } final Client client = new Client(Host.class, new Long(update.getResourceId())); itemManager.runUpdateForEvent(HOST_ENDPOINTS_UPDATE, update, client, new Runnable() { @Override public void run() { Host host = objectManager.loadResource(Host.class, client.getResourceId()); if (host != null && host.getRemoved() == null) { reconcileHostEndpointsImpl(host); } } }); } @Override public void reconcileServiceEndpoints(Service service) { ConfigUpdateRequest request = ConfigUpdateRequest.forResource(Service.class, service.getId()); request.addItem(SERVICE_ENDPOINTS_UPDATE); request.withDeferredTrigger(false); itemManager.updateConfig(request); } @Override public void reconcileHostEndpoints(Host host) { ConfigUpdateRequest request = ConfigUpdateRequest.forResource(Host.class, host.getId()); request.addItem(HOST_ENDPOINTS_UPDATE); request.withDeferredTrigger(false); itemManager.updateConfig(request); } @Override public void removeFromLoadBalancerServices(Service service) { List<? extends Service> balancers = objectManager.find(Service.class, SERVICE.KIND, ServiceConstants.KIND_LOAD_BALANCER_SERVICE, SERVICE.REMOVED, null, SERVICE.ACCOUNT_ID, service.getAccountId()); for (Service balancer : balancers) { LbConfig lbConfig = DataAccessor.field(balancer, ServiceConstants.FIELD_LB_CONFIG, jsonMapper, LbConfig.class); if (lbConfig == null || lbConfig.getPortRules() == null) { continue; } List<PortRule> newSet = new ArrayList<>(); boolean update = false; for (PortRule rule : lbConfig.getPortRules()) { if (rule.getServiceId() == null) { continue; } if (rule.getServiceId().equalsIgnoreCase(service.getId().toString())) { update = true; continue; } newSet.add(rule); } if (update) { lbConfig.setPortRules(newSet); Map<String, Object> data = new HashMap<>(); data.put(ServiceConstants.FIELD_LB_CONFIG, lbConfig); balancer = objectManager.setFields(balancer, data); objectProcessManager.scheduleStandardProcessAsync(StandardProcess.UPDATE, balancer, null); } } } @Override public void registerServiceLinks(Service service) { registerTargetServices(service); registerAsTargetService(service); } private void registerAsTargetService(Service targetService) { List<Long> accountIds = new ArrayList<>(); accountIds.add(targetService.getAccountId()); List<? extends AccountLink> linkedToAccounts = objectManager.find(AccountLink.class, ACCOUNT_LINK.LINKED_ACCOUNT_ID, targetService.getAccountId(), ACCOUNT_LINK.REMOVED, null); // add all accounts that are linked to service's account for (AccountLink linkedToAccount : linkedToAccounts) { accountIds.add(linkedToAccount.getAccountId()); } List<Service> services = new ArrayList<>(); for (Long accountId : accountIds) { services.addAll(objectManager.find(Service.class, SERVICE.ACCOUNT_ID, accountId, SERVICE.REMOVED, null)); } for (Service service : services) { // skip itself if (targetService.getId().equals(service.getId())) { continue; } if (isSelectorLinkMatch(service.getSelectorLink(), targetService)) { addServiceLink(service, targetService); } } } private void registerTargetServices(Service service) { List<Long> targetAccountIds = new ArrayList<>(); targetAccountIds.add(service.getAccountId()); // add all accounts that are linked to service's account List<? extends AccountLink> linkedAccounts = objectManager.find(AccountLink.class, ACCOUNT_LINK.ACCOUNT_ID, service.getAccountId(), ACCOUNT_LINK.REMOVED, null); for (AccountLink linkedAccount : linkedAccounts) { targetAccountIds.add(linkedAccount.getLinkedAccountId()); } List<Service> targetServices = new ArrayList<>(); for (Long accountId : targetAccountIds) { targetServices.addAll(objectManager.find(Service.class, SERVICE.ACCOUNT_ID, accountId, SERVICE.REMOVED, null)); } for (Service targetService : targetServices) { // skip itself if (targetService.getId().equals(service.getId())) { continue; } if (isSelectorLinkMatch(service.getSelectorLink(), targetService)) { addServiceLink(service, targetService); } } } protected void addServiceLink(Service service, Service targetService) { ServiceLink link = new ServiceLink(targetService.getId(), null); addServiceLink(service, link); } }