package io.cattle.platform.core.dao.impl; import static io.cattle.platform.core.model.tables.HostTable.*; import static io.cattle.platform.core.model.tables.HostIpAddressMapTable.*; import static io.cattle.platform.core.model.tables.InstanceHostMapTable.*; import static io.cattle.platform.core.model.tables.InstanceLinkTable.*; import static io.cattle.platform.core.model.tables.InstanceTable.*; import static io.cattle.platform.core.model.tables.IpAddressNicMapTable.*; import static io.cattle.platform.core.model.tables.IpAddressTable.*; import static io.cattle.platform.core.model.tables.NicTable.*; import static io.cattle.platform.core.model.tables.PortTable.*; import static io.cattle.platform.core.model.tables.ServiceExposeMapTable.*; 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.core.addon.PublicEndpoint; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.core.constants.IpAddressConstants; import io.cattle.platform.core.constants.PortConstants; import io.cattle.platform.core.dao.InstanceDao; import io.cattle.platform.core.model.Account; import io.cattle.platform.core.model.Host; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.InstanceHostMap; import io.cattle.platform.core.model.InstanceLink; import io.cattle.platform.core.model.IpAddress; import io.cattle.platform.core.model.Nic; import io.cattle.platform.core.model.Port; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.ServiceExposeMap; import io.cattle.platform.core.model.ServiceIndex; import io.cattle.platform.core.model.Subnet; import io.cattle.platform.core.model.tables.HostTable; import io.cattle.platform.core.model.tables.InstanceTable; import io.cattle.platform.core.model.tables.IpAddressTable; import io.cattle.platform.core.model.tables.PortTable; import io.cattle.platform.core.model.tables.ServiceExposeMapTable; import io.cattle.platform.core.model.tables.ServiceIndexTable; import io.cattle.platform.core.model.tables.SubnetTable; import io.cattle.platform.core.model.tables.records.HostRecord; import io.cattle.platform.core.model.tables.records.InstanceHostMapRecord; import io.cattle.platform.core.model.tables.records.InstanceLinkRecord; import io.cattle.platform.core.model.tables.records.InstanceRecord; import io.cattle.platform.core.model.tables.records.NicRecord; import io.cattle.platform.core.model.tables.records.ServiceRecord; import io.cattle.platform.db.jooq.dao.impl.AbstractJooqDao; import io.cattle.platform.db.jooq.mapper.MultiRecordMapper; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.object.util.DataUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.lang3.StringUtils; import org.jooq.Condition; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @Named public class InstanceDaoImpl extends AbstractJooqDao implements InstanceDao { @Inject ObjectManager objectManager; public static class IpAddressToServiceIndex { ServiceIndex index; IpAddress ipAddress; Subnet subnet; public ServiceIndex getIndex() { return index; } public void setIndex(ServiceIndex index) { this.index = index; } public IpAddress getIpAddress() { return ipAddress; } public void setIpAddress(IpAddress ipAddress) { this.ipAddress = ipAddress; } public Subnet getSubnet() { return subnet; } public void setSubnet(Subnet subnet) { this.subnet = subnet; } public IpAddressToServiceIndex(ServiceIndex index, IpAddress ipAddress, Subnet subnet) { super(); this.index = index; this.ipAddress = ipAddress; this.subnet = subnet; } } LoadingCache<Long, Map<String, Object>> instanceData = CacheBuilder.newBuilder() .expireAfterAccess(24, TimeUnit.HOURS) .build(new CacheLoader<Long, Map<String, Object>>() { @Override public Map<String, Object> load(Long key) throws Exception { return lookupCacheInstanceData(key); } }); @Override public List<? extends Instance> getNonRemovedInstanceOn(Long hostId) { return create() .select(INSTANCE.fields()) .from(INSTANCE) .join(INSTANCE_HOST_MAP) .on(INSTANCE_HOST_MAP.HOST_ID.eq(hostId) .and(INSTANCE_HOST_MAP.INSTANCE_ID.eq(INSTANCE.ID))) .where(INSTANCE.REMOVED.isNull().and( INSTANCE.STATE.notIn(InstanceConstants.STATE_ERROR, InstanceConstants.STATE_ERRORING, CommonStatesConstants.REMOVING))) .fetchInto(InstanceRecord.class); } @Override public Instance getInstanceByUuidOrExternalId(Long accountId, String uuid, String externalId) { Instance instance = null; Condition condition = INSTANCE.ACCOUNT_ID.eq(accountId).and(INSTANCE.STATE.notIn(CommonStatesConstants.PURGED, CommonStatesConstants.PURGING)); if(StringUtils.isNotEmpty(uuid)) { instance = create() .selectFrom(INSTANCE) .where(condition .and(INSTANCE.UUID.eq(uuid))) .fetchAny(); } if (instance == null && StringUtils.isNotEmpty(externalId)) { instance = create() .selectFrom(INSTANCE) .where(condition .and(INSTANCE.EXTERNAL_ID.eq(externalId))) .fetchAny(); } return instance; } @Override public List<? extends Service> findServicesFor(Instance instance) { return create().select(SERVICE.fields()) .from(SERVICE) .join(SERVICE_EXPOSE_MAP) .on(SERVICE_EXPOSE_MAP.SERVICE_ID.eq(SERVICE.ID)) .where(SERVICE_EXPOSE_MAP.INSTANCE_ID.eq(instance.getId())) .fetchInto(ServiceRecord.class); } @Override public List<? extends Service> findServicesNonRemovedLinksOnly(Instance instance) { return create().select(SERVICE.fields()) .from(SERVICE) .join(SERVICE_EXPOSE_MAP) .on(SERVICE_EXPOSE_MAP.SERVICE_ID.eq(SERVICE.ID)) .where(SERVICE_EXPOSE_MAP.INSTANCE_ID.eq(instance.getId()) .and(SERVICE_EXPOSE_MAP.REMOVED.isNull())) .fetchInto(ServiceRecord.class); } @Override public List<? extends Instance> listNonRemovedInstances(Account account, boolean forService) { List<? extends Instance> serviceInstances = create().select(INSTANCE.fields()) .from(INSTANCE) .join(SERVICE_EXPOSE_MAP) .on(SERVICE_EXPOSE_MAP.INSTANCE_ID.eq(INSTANCE.ID)) .where(INSTANCE.ACCOUNT_ID.eq(account.getId())) .and(INSTANCE.REMOVED.isNull()) .fetchInto(InstanceRecord.class); if (forService) { return serviceInstances; } List<? extends Instance> allInstances = create().select(INSTANCE.fields()) .from(INSTANCE) .where(INSTANCE.ACCOUNT_ID.eq(account.getId())) .and(INSTANCE.REMOVED.isNull()) .fetchInto(InstanceRecord.class); allInstances.removeAll(serviceInstances); return allInstances; } @Override public List<? extends Instance> findInstancesFor(Service service) { return create() .select(INSTANCE.fields()) .from(INSTANCE) .join(SERVICE_EXPOSE_MAP) .on(SERVICE_EXPOSE_MAP.INSTANCE_ID.eq(INSTANCE.ID) .and(SERVICE_EXPOSE_MAP.SERVICE_ID.eq(service.getId())) .and(SERVICE_EXPOSE_MAP.STATE.in(CommonStatesConstants.ACTIVATING, CommonStatesConstants.ACTIVE, CommonStatesConstants.REQUESTED)) .and(INSTANCE.STATE.notIn(CommonStatesConstants.PURGING, CommonStatesConstants.PURGED, CommonStatesConstants.REMOVED, CommonStatesConstants.REMOVING))) .fetchInto(InstanceRecord.class); } @Override public List<? extends Instance> findInstanceByServiceName(long accountId, String serviceName) { return create().select(INSTANCE.fields()) .from(INSTANCE) .join(SERVICE_EXPOSE_MAP) .on(INSTANCE.ID.eq(SERVICE_EXPOSE_MAP.INSTANCE_ID)) .join(SERVICE) .on(SERVICE.ID.eq(SERVICE_EXPOSE_MAP.SERVICE_ID)) .where(INSTANCE.STATE.eq(InstanceConstants.STATE_RUNNING) .and(INSTANCE.ACCOUNT_ID.eq(accountId)) .and(SERVICE_EXPOSE_MAP.REMOVED.isNull()) .and(SERVICE.REMOVED.isNull()) .and(SERVICE.NAME.eq(serviceName))) .fetchInto(InstanceRecord.class); } @Override public List<? extends Instance> findInstanceByServiceName(long accountId, String serviceName, String stackName) { return create().select(INSTANCE.fields()) .from(INSTANCE) .join(SERVICE_EXPOSE_MAP) .on(INSTANCE.ID.eq(SERVICE_EXPOSE_MAP.INSTANCE_ID)) .join(SERVICE) .on(SERVICE.ID.eq(SERVICE_EXPOSE_MAP.SERVICE_ID)) .join(STACK) .on(STACK.ID.eq(SERVICE.STACK_ID)) .where(INSTANCE.STATE.eq(InstanceConstants.STATE_RUNNING) .and(INSTANCE.ACCOUNT_ID.eq(accountId)) .and(SERVICE_EXPOSE_MAP.REMOVED.isNull()) .and(SERVICE.REMOVED.isNull()) .and(SERVICE.NAME.eq(serviceName)) .and(STACK.NAME.eq(stackName))) .fetchInto(InstanceRecord.class); } @Override public List<? extends Instance> findUnallocatedInstanceByDeploymentUnitUuid(long accountId, String deploymentUnitUuid) { return create().select(INSTANCE.fields()) .from(INSTANCE) .leftOuterJoin(INSTANCE_HOST_MAP) .on(INSTANCE_HOST_MAP.INSTANCE_ID.eq(INSTANCE.ID)) .where( INSTANCE.REMOVED.isNull() .and(INSTANCE_HOST_MAP.ID.isNull()) .and(INSTANCE.DEPLOYMENT_UNIT_UUID.eq(deploymentUnitUuid)) .and(INSTANCE.ALLOCATION_STATE.eq(CommonStatesConstants.INACTIVE))) .fetchInto(InstanceRecord.class); } @Override public List<? extends Host> findHosts(long accountId, long instanceId) { return create().select(HOST.fields()) .from(INSTANCE) .join(INSTANCE_HOST_MAP) .on(INSTANCE.ID.eq(INSTANCE_HOST_MAP.INSTANCE_ID)) .join(HOST) .on(HOST.ID.eq(INSTANCE_HOST_MAP.HOST_ID)) .where(HOST.REMOVED.isNull() .and(INSTANCE_HOST_MAP.REMOVED.isNull()) .and(INSTANCE.ID.eq(instanceId))) .fetchInto(HostRecord.class); } protected Map<String, Object> lookupCacheInstanceData(long instanceId) { Instance instance = objectManager.loadResource(Instance.class, instanceId); if (instance == null) { return Collections.emptyMap(); } Map<String, Object> newData = new HashMap<>(); newData.put(DataUtils.FIELDS, instance.getData().get(DataUtils.FIELDS)); return newData; } @Override public Map<String, Object> getCacheInstanceData(long instanceId) { return instanceData.getUnchecked(instanceId); } @Override public void clearCacheInstanceData(long instanceId) { instanceData.invalidate(instanceId); } @Override public List<PublicEndpoint> getPublicEndpoints(long accountId, Long serviceId, Long hostId) { List<PublicEndpoint> toReturn = new ArrayList<>(); for (PublicEndpoint ep : getPublicEndpointsInternal(accountId, serviceId, hostId)) { if (ep.getHostId() != null && ep.getInstanceId() != null && !StringUtils.isEmpty(ep.getIpAddress())) { toReturn.add(ep); } } return toReturn; } private List<PublicEndpoint> getPublicEndpointsInternal(long accountId, Long serviceId, Long hostId) { MultiRecordMapper<PublicEndpoint> mapper = new MultiRecordMapper<PublicEndpoint>() { @Override protected PublicEndpoint map(List<Object> input) { Instance instance = (Instance) input.get(0); Port port = (Port) input.get(1); Host host = (Host) input.get(2); String address = ""; address = DataAccessor.fieldString(port, PortConstants.FIELD_BIND_ADDR); if (StringUtils.isEmpty(address) || "0.0.0.0".equals(address)) { IpAddress ip = (IpAddress) input.get(3); if (ip != null) { address = ip.getAddress(); } else { IpAddress hostIp = (IpAddress) input.get(4); if (hostIp != null) { address = hostIp.getAddress(); } } } ServiceExposeMap exposeMap = (ServiceExposeMap) input.get(5); Long serviceId = exposeMap != null ? exposeMap.getServiceId() : null; PublicEndpoint data = new PublicEndpoint(address, port.getPublicPort(), host.getId(), instance.getId(), serviceId); return data; } }; InstanceTable instance = mapper.add(INSTANCE, INSTANCE.ID, INSTANCE.ACCOUNT_ID); PortTable port = mapper.add(PORT); HostTable host = mapper.add(HOST, HOST.ID); IpAddressTable ipAddress = mapper.add(IP_ADDRESS, IP_ADDRESS.ID, IP_ADDRESS.ADDRESS); IpAddressTable hostIp = mapper.add(IP_ADDRESS, IP_ADDRESS.ID, IP_ADDRESS.ADDRESS); ServiceExposeMapTable exposeMap = mapper.add(SERVICE_EXPOSE_MAP, SERVICE_EXPOSE_MAP.INSTANCE_ID, SERVICE_EXPOSE_MAP.SERVICE_ID); Condition condition = null; if (serviceId != null && hostId != null) { condition = host.ID.eq(hostId).and(exposeMap.SERVICE_ID.eq(serviceId)); } else if (hostId != null) { condition = host.ID.eq(hostId); } else if (serviceId != null) { condition = (exposeMap.SERVICE_ID.eq(serviceId)); } return create() .select(mapper.fields()) .from(instance) .join(port) .on(port.INSTANCE_ID.eq(instance.ID)) .join(INSTANCE_HOST_MAP) .on(INSTANCE_HOST_MAP.INSTANCE_ID.eq(instance.ID)) .join(host) .on(INSTANCE_HOST_MAP.HOST_ID.eq(host.ID)) .leftOuterJoin(ipAddress) .on(port.PUBLIC_IP_ADDRESS_ID.eq(ipAddress.ID)) .leftOuterJoin(exposeMap) .on(exposeMap.INSTANCE_ID.eq(instance.ID)) .leftOuterJoin(HOST_IP_ADDRESS_MAP) .on(host.ID.eq(HOST_IP_ADDRESS_MAP.HOST_ID)) .leftOuterJoin(hostIp) .on(HOST_IP_ADDRESS_MAP.IP_ADDRESS_ID.eq(hostIp.ID)) .where(instance.ACCOUNT_ID.eq(accountId)) .and(instance.REMOVED.isNull()) .and(port.REMOVED.isNull()) .and(host.REMOVED.isNull()) .and(ipAddress.REMOVED.isNull()) .and(exposeMap.REMOVED.isNull()) .and(port.PUBLIC_PORT.isNotNull()) .and(port.STATE.in(CommonStatesConstants.ACTIVATING, CommonStatesConstants.ACTIVE, CommonStatesConstants.UPDATING_ACTIVE)) .and(condition) .fetch().map(mapper); } @Override public List<IpAddressToServiceIndex> getIpToIndex(Service service) { MultiRecordMapper<IpAddressToServiceIndex> mapper = new MultiRecordMapper<IpAddressToServiceIndex>() { @Override protected IpAddressToServiceIndex map(List<Object> input) { ServiceIndex index = (ServiceIndex) input.get(0); IpAddress ip = (IpAddress) input.get(1); Subnet subnet = (Subnet) input.get(2); IpAddressToServiceIndex data = new IpAddressToServiceIndex(index, ip, subnet); return data; } }; ServiceIndexTable serviceIndex = mapper.add(SERVICE_INDEX); IpAddressTable ipAddress = mapper.add(IP_ADDRESS); SubnetTable subnet = mapper.add(SUBNET); ServiceExposeMapTable exposeMap = mapper.add(SERVICE_EXPOSE_MAP, SERVICE_EXPOSE_MAP.REMOVED); return create() .select(mapper.fields()) .from(INSTANCE) .join(exposeMap) .on(exposeMap.INSTANCE_ID.eq(INSTANCE.ID)) .join(NIC) .on(NIC.INSTANCE_ID.eq(exposeMap.INSTANCE_ID)) .join(IP_ADDRESS_NIC_MAP) .on(IP_ADDRESS_NIC_MAP.NIC_ID.eq(NIC.ID)) .join(ipAddress) .on(IP_ADDRESS_NIC_MAP.IP_ADDRESS_ID.eq(ipAddress.ID)) .join(serviceIndex) .on(serviceIndex.ID.eq(INSTANCE.SERVICE_INDEX_ID)) .join(subnet) .on(ipAddress.SUBNET_ID.eq(subnet.ID)) .where(exposeMap.SERVICE_ID.eq(service.getId())) .and(exposeMap.REMOVED.isNull()) .and(NIC.REMOVED.isNull()) .and(ipAddress.REMOVED.isNull()) .and(ipAddress.ADDRESS.isNotNull()) .and(INSTANCE.REMOVED.isNull()) .and(ipAddress.ROLE.eq(IpAddressConstants.ROLE_PRIMARY)) .fetch().map(mapper); } @Override public List<? extends Instance> findBadInstances(int count) { return create().select(INSTANCE.fields()) .from(INSTANCE) .join(INSTANCE_HOST_MAP) .on(INSTANCE_HOST_MAP.INSTANCE_ID.eq(INSTANCE.ID)) .join(HOST) .on(INSTANCE_HOST_MAP.HOST_ID.eq(HOST.ID)) .where(HOST.REMOVED.isNotNull().and(INSTANCE.REMOVED.isNull()) .and(INSTANCE.STATE.notIn(InstanceConstants.STATE_STOPPING, CommonStatesConstants.REMOVING))) .limit(count) .fetchInto(InstanceRecord.class); } @Override public List<? extends InstanceHostMap> findBadInstanceHostMaps(int count) { return create().select(INSTANCE_HOST_MAP.fields()) .from(INSTANCE_HOST_MAP) .join(INSTANCE) .on(INSTANCE_HOST_MAP.INSTANCE_ID.eq(INSTANCE.ID)) .where(INSTANCE_HOST_MAP.REMOVED.isNull() .and(INSTANCE.STATE.eq(CommonStatesConstants.PURGED)) .and(INSTANCE_HOST_MAP.STATE.notIn(CommonStatesConstants.DEACTIVATING, CommonStatesConstants.REMOVING))) .limit(count) .fetchInto(InstanceHostMapRecord.class); } @Override public List<? extends Nic> findBadNics(int count) { return create().select(NIC.fields()) .from(NIC) .join(INSTANCE) .on(INSTANCE.ID.eq(NIC.INSTANCE_ID)) .where(NIC.REMOVED.isNull().and(INSTANCE.STATE.eq(CommonStatesConstants.PURGED)) .and(NIC.STATE.notIn(CommonStatesConstants.DEACTIVATING, CommonStatesConstants.REMOVING))) .limit(count) .fetchInto(NicRecord.class); } @Override public List<? extends InstanceLink> findBadInstanceLinks(int count) { return create().select(INSTANCE_LINK.fields()) .from(INSTANCE_LINK) .join(INSTANCE) .on(INSTANCE.ID.eq(INSTANCE_LINK.TARGET_INSTANCE_ID)) .where(INSTANCE.STATE.eq(CommonStatesConstants.PURGED)) .limit(count) .fetchInto(InstanceLinkRecord.class); } }