package io.cattle.platform.agent.server.resource.impl;
import static io.cattle.platform.core.model.tables.HostTable.*;
import static io.cattle.platform.core.model.tables.PhysicalHostTable.*;
import io.cattle.platform.agent.instance.service.AgentMetadataService;
import io.cattle.platform.agent.server.ping.dao.PingDao;
import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.core.constants.AgentConstants;
import io.cattle.platform.core.constants.HostConstants;
import io.cattle.platform.core.constants.IpAddressConstants;
import io.cattle.platform.core.constants.StoragePoolConstants;
import io.cattle.platform.core.dao.AgentDao;
import io.cattle.platform.core.dao.GenericResourceDao;
import io.cattle.platform.core.dao.IpAddressDao;
import io.cattle.platform.core.dao.StoragePoolDao;
import io.cattle.platform.core.model.Agent;
import io.cattle.platform.core.model.Host;
import io.cattle.platform.core.model.IpAddress;
import io.cattle.platform.core.model.PhysicalHost;
import io.cattle.platform.core.model.StoragePool;
import io.cattle.platform.eventing.EventService;
import io.cattle.platform.eventing.annotation.AnnotatedEventListener;
import io.cattle.platform.eventing.annotation.EventHandler;
import io.cattle.platform.eventing.model.Event;
import io.cattle.platform.eventing.model.EventVO;
import io.cattle.platform.framework.event.FrameworkEvents;
import io.cattle.platform.framework.event.Ping;
import io.cattle.platform.lock.LockCallbackNoReturn;
import io.cattle.platform.lock.LockManager;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.meta.ObjectMetaDataManager;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.util.type.CollectionUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jooq.exception.DataChangedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.netflix.config.DynamicLongProperty;
public class AgentResourcesMonitor implements AnnotatedEventListener {
private static final Logger log = LoggerFactory.getLogger(AgentResourcesMonitor.class);
private static final DynamicLongProperty CACHE_RESOURCE = ArchaiusUtil.getLong("agent.resource.monitor.cache.resource.seconds");
private static final String[] UPDATABLE_HOST_FIELDS = new String[] {
HostConstants.FIELD_API_PROXY,
HostConstants.FIELD_HOSTNAME,
HostConstants.FIELD_INFO,
HostConstants.FIELD_LABELS };
private static final Set<String> ORCHESTRATE_FIELDS = new HashSet<>(Arrays.asList(HostConstants.FIELD_LABELS));
@Inject
PingDao pingDao;
@Inject
AgentDao agentDao;
@Inject
GenericResourceDao resourceDao;
@Inject
StoragePoolDao storagePoolDao;
@Inject
IpAddressDao ipAddressDao;
@Inject
ObjectManager objectManager;
@Inject
LockManager lockManager;
@Inject
AgentMetadataService agentMetadataService;
@Inject
EventService eventService;
Cache<String, Boolean> resourceCache;
public AgentResourcesMonitor() {
super();
buildCache();
CACHE_RESOURCE.addCallback(new Runnable() {
@Override
public void run() {
buildCache();
}
});
}
protected void buildCache() {
resourceCache = CacheBuilder.newBuilder().expireAfterWrite(CACHE_RESOURCE.get(), TimeUnit.SECONDS).build();
}
@EventHandler
public void pingReply(Ping ping) {
processPingReply(ping);
}
public void processPingReply(Ping ping) {
String agentIdStr = ping.getResourceId();
if (agentIdStr == null) {
return;
}
long agentId = Long.parseLong(agentIdStr);
if (ping.getData() == null) {
return;
}
final AgentResources resources = processResources(ping);
if (!resources.hasContent()) {
return;
}
Boolean done = resourceCache.getIfPresent(resources.getHash());
if (done != null && done.booleanValue()) {
return;
}
final Agent agent = objectManager.loadResource(Agent.class, agentId);
lockManager.lock(new AgentResourceCreateLock(agent), new LockCallbackNoReturn() {
@Override
public void doWithLockNoResult() {
Boolean done = resourceCache.getIfPresent(resources.getHash());
if (done != null && done.booleanValue()) {
return;
}
Map<String, Host> hosts = setHosts(agent, resources);
setStoragePools(hosts, agent, resources);
setIpAddresses(hosts, agent, resources);
resourceCache.put(resources.getHash(), true);
}
});
}
protected Map<String, StoragePool> setStoragePools(Map<String, Host> hosts, Agent agent, AgentResources resources) {
Map<String, StoragePool> pools = agentDao.getStoragePools(agent.getId());
for (Map.Entry<String, Map<String, Object>> poolData : resources.getStoragePools().entrySet()) {
String uuid = poolData.getKey();
Map<String, Object> data = poolData.getValue();
if (pools.containsKey(uuid)) {
continue;
}
Host host = hosts.get(ObjectUtils.toString(data.get(HostConstants.FIELD_HOST_UUID), null));
if (host == null) {
continue;
}
data = createData(agent, uuid, data);
pools.put(uuid, storagePoolDao.mapNewPool(host, data));
}
return pools;
}
protected void setIpAddresses(Map<String, Host> hosts, Agent agent, AgentResources resources) {
for (Map.Entry<String, Map<String, Object>> ipData : resources.getIpAddresses().entrySet()) {
String address = ipData.getKey();
Map<String, Object> data = ipData.getValue();
Host host = hosts.get(ObjectUtils.toString(data.get(HostConstants.FIELD_HOST_UUID), null));
if (host == null) {
continue;
}
List<IpAddress> ips = objectManager.mappedChildren(host, IpAddress.class);
if (ips.size() == 0) {
ipAddressDao.assignAndActivateNewAddress(host, address);
} else {
boolean publish = false;
IpAddress ip = ips.get(0);
if (!address.equalsIgnoreCase(ip.getAddress())) {
publish = true;
ipAddressDao.updateIpAddress(ip, address);
}
String currentIp = DataAccessor.fieldString(host, HostConstants.FIELD_IP_ADDRESS);
if (!ObjectUtils.equals(currentIp, ip.getAddress())) {
try {
objectManager.setFields(host, HostConstants.FIELD_IP_ADDRESS, ip.getAddress());
publish = true;
} catch (DataChangedException e) {
}
}
if (publish) {
publishChanged(host);
agentMetadataService.updateMetadata(host.getAccountId());
}
}
}
}
protected void publishChanged(Host host) {
Map<String, Object> eventData = CollectionUtils.asMap(ObjectMetaDataManager.ACCOUNT_FIELD, host.getAccountId());
// send host update event
Event event = EventVO.newEvent(FrameworkEvents.STATE_CHANGE)
.withData(eventData)
.withResourceType(HostConstants.TYPE)
.withResourceId(host.getId().toString());
eventService.publish(event);
}
protected Map<String, Host> setHosts(Agent agent, AgentResources resources) {
Map<String, Host> hosts = agentDao.getHosts(agent.getId());
for (Map.Entry<String, Map<String, Object>> hostData : resources.getHosts().entrySet()) {
String uuid = hostData.getKey();
Map<String, Object> data = hostData.getValue();
String physicalHostUuid = ObjectUtils.toString(data.get(HostConstants.FIELD_PHYSICAL_HOST_UUID), null);
Long physicalHostId = getPhysicalHost(agent, physicalHostUuid, new HashMap<String, Object>());
boolean orchestrate = false;
if (!hosts.containsKey(uuid) && physicalHostId != null) {
Host host = objectManager.findAny(Host.class,
HOST.PHYSICAL_HOST_ID, physicalHostId,
HOST.REMOVED, null);
if (host != null) {
String reported = DataAccessor.fieldString(host, HostConstants.FIELD_REPORTED_UUID);
if (uuid.equals(reported)) {
hosts.put(reported, host);
}
}
}
if (hosts.containsKey(uuid)) {
Map<Object, Object> updates = new HashMap<>();
Host host = hosts.get(uuid);
if (physicalHostId != null) {
/* Add create labels and assign the agent ID */
if (physicalHostId.equals(host.getPhysicalHostId()) && host.getAgentId() == null) {
PhysicalHost physicalHost = objectManager.loadResource(PhysicalHost.class, physicalHostId);
/* Copy createLabels to labels */
Map<String, Object> labels = CollectionUtils.toMap(data.get(HostConstants.FIELD_LABELS));
labels.putAll(CollectionUtils.<String, Object>toMap(data.get(HostConstants.FIELD_CREATE_LABELS)));
updates.putAll(createData(agent, uuid, data));
updates.put(HostConstants.FIELD_LABELS, labels);
updates.put(HostConstants.FIELD_AGENT_ID, physicalHost.getAgentId());
}
if (!physicalHostId.equals(host.getPhysicalHostId())) {
updates.put(HOST.PHYSICAL_HOST_ID, physicalHostId);
}
}
for (String key : UPDATABLE_HOST_FIELDS) {
Object value = data.get(key);
if (value == null || StringUtils.isBlank(value.toString())) {
continue;
}
Object existingValue = io.cattle.platform.object.util.ObjectUtils.getValue(host, key);
if (value instanceof Map && existingValue instanceof Map) {
Map<Object, Object> newValueMap = new HashMap<>((Map<?, ?>)existingValue);
newValueMap.putAll((Map<?, ?>)value);
value = newValueMap;
}
if (ObjectUtils.notEqual(value, existingValue)) {
if (ORCHESTRATE_FIELDS.contains(key)) {
orchestrate = true;
}
updates.put(key, value);
}
}
if (host.getMemory() == null) {
Long memory = DataAccessor.fromMap(data).withKey(HostConstants.FIELD_MEMORY).as(Long.class);
if (memory != null) {
updates.put(HostConstants.FIELD_MEMORY, memory);
}
} else {
updates.remove(HostConstants.FIELD_MEMORY);
}
if (host.getMilliCpu() == null) {
Long cpu = DataAccessor.fromMap(data).withKey(HostConstants.FIELD_MILLI_CPU).as(Long.class);
if (cpu != null) {
updates.put(HostConstants.FIELD_MILLI_CPU, cpu);
}
} else {
updates.remove(HostConstants.FIELD_MILLI_CPU);
}
if (host.getLocalStorageMb() == null) {
Long storage = DataAccessor.fromMap(data).withKey(HostConstants.FIELD_LOCAL_STORAGE_MB).as(Long.class);
if (storage != null) {
updates.put(HostConstants.FIELD_LOCAL_STORAGE_MB, storage);
}
} else {
updates.remove(HostConstants.FIELD_LOCAL_STORAGE_MB);
}
if (updates.size() > 0) {
host = objectManager.reload(host);
Map<String, Object> updateFields = objectManager.convertToPropertiesFor(host, updates);
if (orchestrate && !HostConstants.STATE_PROVISIONING.equals(host.getState())) {
resourceDao.updateAndSchedule(host, updateFields);
} else {
try {
objectManager.setFields(host, updateFields);
publishChanged(host);
} catch (DataChangedException e) {
/*
* Various reasons why this might conflict with other processes, but here
* we can safely ignore it.
*/
}
}
}
} else {
data = createData(agent, uuid, data);
data.put(HostConstants.FIELD_PHYSICAL_HOST_ID, physicalHostId);
/* Copy createLabels to labels */
Map<String, Object> labels = CollectionUtils.toMap(data.get(HostConstants.FIELD_LABELS));
labels.putAll(CollectionUtils.<String, Object>toMap(data.get(HostConstants.FIELD_CREATE_LABELS)));
data.put(HostConstants.FIELD_LABELS, labels);
hosts.put(uuid, resourceDao.createAndSchedule(Host.class, data));
}
}
return hosts;
}
protected Long getPhysicalHost(Agent agent, String uuid, Map<String, Object> properties) {
if (uuid == null) {
return null;
}
Map<String, PhysicalHost> hosts = agentDao.getPhysicalHosts(agent.getId());
PhysicalHost host = hosts.get(uuid);
if (host != null) {
return host.getId();
}
host = objectManager.findAny(PhysicalHost.class, PHYSICAL_HOST.UUID, uuid);
if (host != null && host.getRemoved() == null) {
Long agentId = DataAccessor.fields(host).withKey(AgentConstants.ID_REF).as(Long.class);
// For security purposes, ensure the agentIds match.
if (agentId != null && agentId.longValue() == agent.getId()) {
host.setAgentId(agent.getId());
DataAccessor.fields(host).withKey(AgentConstants.ID_REF).remove();
objectManager.persist(host);
return host.getId();
}
} else if (host == null) {
host = objectManager.findAny(PhysicalHost.class, PHYSICAL_HOST.EXTERNAL_ID, uuid);
// For security purposes, only allow this type of assignment if the
// host doesn't yet have an agentId
if (host != null && host.getAgentId() == null && host.getRemoved() == null) {
host.setAgentId(agent.getId());
DataAccessor.fields(host).withKey(AgentConstants.ID_REF).remove();
objectManager.persist(host);
return host.getId();
}
}
Map<String, Object> data = createData(agent, uuid, properties);
host = resourceDao.createAndSchedule(PhysicalHost.class, data);
return host.getId();
}
protected Map<String, Object> createData(Agent agent, String uuid, Map<String, Object> data) {
Map<String, Object> properties = new HashMap<>(data);
properties.put(HostConstants.FIELD_REPORTED_UUID, uuid);
properties.remove(ObjectMetaDataManager.UUID_FIELD);
Long accountId = DataAccessor.fromDataFieldOf(agent).withKey(AgentConstants.DATA_AGENT_RESOURCES_ACCOUNT_ID).as(Long.class);
if (accountId == null) {
accountId = agent.getAccountId();
}
properties.put(ObjectMetaDataManager.ACCOUNT_FIELD, accountId);
properties.put(AgentConstants.ID_REF, agent.getId());
return properties;
}
protected AgentResources processResources(Ping ping) {
AgentResources resources = new AgentResources();
List<Map<String, Object>> pingData = ping.getData().getResources();
if (pingData == null) {
return resources;
}
for (Map<String, Object> resource : pingData) {
final String type = ObjectUtils.toString(resource.get(ObjectMetaDataManager.TYPE_FIELD), null);
final String uuid = ObjectUtils.toString(resource.get(ObjectMetaDataManager.UUID_FIELD), null);
if (type == null || uuid == null) {
log.error("type [{}] or uuid [{}] is null for resource on pong from agent [{}]", type, uuid, ping.getResourceId());
continue;
}
if (type.equals(HostConstants.TYPE)) {
resources.getHosts().put(uuid, resource);
} else if (type.equals(StoragePoolConstants.TYPE)) {
resources.getStoragePools().put(uuid, resource);
} else if (type.equals(IpAddressConstants.TYPE)) {
resources.getIpAddresses().put(uuid, resource);
}
}
return resources;
}
}