package io.cattle.platform.metadata.service.impl;
import static io.cattle.platform.util.type.CollectionUtils.*;
import io.cattle.platform.core.constants.InstanceConstants;
import io.cattle.platform.core.constants.IpAddressConstants;
import io.cattle.platform.core.constants.NetworkConstants;
import io.cattle.platform.core.model.Credential;
import io.cattle.platform.core.model.Image;
import io.cattle.platform.core.model.Instance;
import io.cattle.platform.core.model.IpAddress;
import io.cattle.platform.core.model.Network;
import io.cattle.platform.core.model.Nic;
import io.cattle.platform.core.model.Subnet;
import io.cattle.platform.core.model.Volume;
import io.cattle.platform.core.model.Zone;
import io.cattle.platform.core.util.HostnameGenerator;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.metadata.dao.MetadataDao;
import io.cattle.platform.metadata.data.MetadataEntry;
import io.cattle.platform.metadata.service.MetadataService;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.object.util.ObjectUtils;
import io.cattle.platform.util.type.CollectionUtils;
import io.github.ibuildthecloud.gdapi.id.IdFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
public class MetadataServiceImpl implements MetadataService {
@Inject
ObjectManager objectManager;
@Inject
MetadataDao metadataDao;
@Inject
JsonMapper jsonMapper;
@Override
public Map<String, Object> getOsMetadata(Instance instance, Map<String, Object> metadata) {
Map<String, Object> data = new HashMap<>();
data.put("availability_zone", "nova");
data.put("files", Collections.EMPTY_LIST);
data.put("public_keys", new HashMap<>());
data.put("meta", new HashMap<>());
data.put("uuid", instance.getUuid());
String hostname = org.apache.commons.lang3.ObjectUtils.toString(metadata.get("hostname"), null);
if (hostname != null) {
data.put("hostname", hostname);
data.put("name", hostname.split("[.]")[0]);
}
Map<String, Object> keys = CollectionUtils.toMap(metadata.get("public-keys"));
for (Map.Entry<String, Object> entry : keys.entrySet()) {
Map<String, Object> value = CollectionUtils.toMap(entry.getValue());
String name = entry.getKey();
if (name.contains("=")) {
name = name.split("=", 2)[1];
keys.put(name, value.get("openssh-key"));
}
}
return data;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> getMetadataForInstance(Instance instance, IdFormatter idFormatter) {
MetadataEntry entry = metadataDao.getMetadataForInstance(instance);
if (entry == null) {
return null;
}
Map<String, Object> data = getMetaData(idFormatter, Arrays.asList(entry));
if (data.size() == 0) {
return null;
}
Object value = data.values().iterator().next();
if (value instanceof Map<?, ?>) {
return CollectionUtils.toMap(((Map<String, Object>) value).get("meta-data"));
}
return null;
}
protected Map<String, Object> getMetaData(IdFormatter idFormatter, List<MetadataEntry> entries) {
Map<Long, String> primaryIps = new HashMap<Long, String>();
Map<Long, Map<String, Object>> instanceMetadatas = new HashMap<Long, Map<String, Object>>();
Map<Long, String> userData = new HashMap<Long, String>();
for (MetadataEntry entry : entries) {
Instance instance = entry.getInstance();
Nic nic = entry.getNic();
IpAddress localIp = entry.getLocalIp();
Credential credential = entry.getCredential();
Network network = entry.getNetwork();
Subnet subnet = entry.getSubnet();
Volume volume = entry.getVolume();
Map<String, Object> instanceMetadata = instanceMetadatas.get(instance.getId());
if (instanceMetadata == null) {
instanceMetadata = populateInstance(idFormatter, instance, network);
instanceMetadatas.put(instance.getId(), instanceMetadata);
}
populateCredential(instanceMetadata, credential);
populateNic(idFormatter, instanceMetadata, nic, network);
populateIps(idFormatter, primaryIps, instanceMetadata, instance, nic, network, subnet, localIp);
populateVolume(instanceMetadata, instance, volume);
userData.put(instance.getId(), instance.getUserdata());
}
Map<String, Object> metadata = new HashMap<String, Object>();
for (Map.Entry<Long, Map<String, Object>> entry : instanceMetadatas.entrySet()) {
Map<String, Object> fullData = new HashMap<String, Object>();
fullData.put("meta-data", entry.getValue());
fullData.put("user-data", userData.get(entry.getKey()));
String key = primaryIps.get(entry.getKey());
if (key != null) {
metadata.put(key, fullData);
}
}
return metadata;
}
protected void populateVolume(Map<String, Object> instanceMetadata, Instance instance, Volume volume) {
if (volume == null) {
return;
}
Integer deviceNumber = volume.getDeviceNumber();
if (deviceNumber == null) {
return;
}
char index = (char) ('a' + deviceNumber.intValue());
if (deviceNumber == 0) {
setNestedValue(instanceMetadata, String.format("/dev/sd%s", index), "block-device-mapping", "ami");
setNestedValue(instanceMetadata, String.format("/dev/sd%s1", index), "block-device-mapping", "root");
} else {
setNestedValue(instanceMetadata, "sd" + index, "block-device-mapping", "ebs" + (deviceNumber + 1));
}
// TODO do something about swap and ephemeral
}
protected void populateIps(IdFormatter idFormatter, Map<Long, String> primaryIps, Map<String, Object> instanceMetadata, Instance instance, Nic nic,
Network network, Subnet subnet, IpAddress localIp) {
boolean firstNic = (org.apache.commons.lang3.ObjectUtils.equals(nic.getDeviceNumber(), 0));
boolean primaryIp = localIp == null ? firstNic : IpAddressConstants.ROLE_PRIMARY.equals(localIp.getRole());
String localIpAddress = localIp == null ? null : localIp.getAddress();
String addressKey = localIpAddress == null ? nic.getMacAddress() : localIpAddress;
String localHostname = HostnameGenerator.lookup(true, instance, addressKey, network);
if (firstNic && primaryIp) {
instanceMetadata.put("hostname", localHostname);
String existingKey = primaryIps.get(instance.getId());
if (existingKey == null || existingKey.contains(":")) { // It's a MAC address
primaryIps.put(instance.getId(), addressKey);
}
}
@SuppressWarnings("unchecked")
Map<String, Object> nicData = (Map<String, Object>) CollectionUtils.getNestedValue(instanceMetadata, "network", "interfaces", "macs", nic
.getMacAddress());
if (nicData == null) {
return;
}
setIfTrueOrNull(primaryIp, nicData, "local-hostname", localHostname);
if (localIpAddress != null) {
append(nicData, "local-ipv4s", localIpAddress);
}
if (firstNic) {
setIfTrueOrNull(primaryIp, instanceMetadata, "local-hostname", localHostname);
setIfTrueOrNull(primaryIp, instanceMetadata, "local-ipv4", localIpAddress);
if (localIp != null && localIp.getSubnetId() != null && subnet != null && StringUtils.isNotBlank(subnet.getGateway())) {
setIfTrueOrNull(primaryIp, instanceMetadata, "local-ipv4-gateway", subnet.getGateway());
}
}
if (subnet != null) {
nicData.put("subnet-id", formatId(idFormatter, subnet));
nicData.put("subnet-ipv4-cidr-block", String.format("%s/%d", subnet.getNetworkAddress(), subnet.getCidrSize()));
if (nicData.containsKey("vpc-id") && nicData.get("vpc-ipv4-cidr-block") == null) {
nicData.put("vpc-ipv4-cidr-block", String.format("%s/%d", subnet.getNetworkAddress(), subnet.getCidrSize()));
}
}
// TODO don't have a security group concept yet
nicData.put("security-group-ids", "");
nicData.put("security-groups", "");
}
protected void setIfTrueOrNull(boolean condition, Map<String, Object> map, String key, String value) {
if (value == null) {
return;
}
if (condition) {
map.put(key, value);
} else {
Object existing = map.get(key);
if (existing == null) {
map.put(key, value);
}
}
}
protected void append(Map<String, Object> map, String key, String data) {
if (data == null) {
return;
}
Object existing = map.get(key);
if (existing == null) {
map.put(key, data);
} else {
map.put(key, String.format("%s\n%s", existing.toString(), data));
}
}
protected void populateCredential(Map<String, Object> instanceMetadata, Credential credential) {
if (credential == null) {
return;
}
int count = 0;
@SuppressWarnings("unchecked")
Map<String, Object> creds = (Map<String, Object>) getNestedValue(instanceMetadata, "public-keys");
if (creds != null) {
count = creds.size();
}
String name = credential.getName();
if (name == null) {
name = "sshkey" + count;
}
setNestedValue(instanceMetadata, credential.getPublicValue(), "public-keys", count + "=" + name, "openssh-key");
}
protected Map<String, Object> populateInstance(IdFormatter idFormatter, Instance instance, Network network) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("ami-id", formatId(idFormatter, Image.class, instance.getImageId()));
data.put("ami-manifest-path", "(unknown)");
data.put("instance-action", "none");
data.put("instance-id", "i-" + formatId(idFormatter, instance));
data.put("instance-type", getInstanceType(instance));
data.put("kernel-id", formatId(idFormatter, instance));
setNestedValue(data, getZone(idFormatter, instance), "placement", "availability-zone");
// TODO don't really know what values are valid here
data.put("profile", "default-paravirtual");
data.put("ami-launch-index", instance.getCreateIndex());
// TODO don't have a concept of reservations
data.put("reservation-id", formatId(idFormatter, instance));
return data;
}
protected void populateNic(IdFormatter idFormatter, Map<String, Object> instanceData, Nic nic, Network network) {
Integer deviceNumber = nic.getDeviceNumber();
String mac = nic.getMacAddress();
if (mac == null) {
return;
}
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) CollectionUtils.getNestedValue(instanceData, "network", "interfaces", "macs", mac);
if (data == null) {
data = new HashMap<String, Object>();
setNestedValue(instanceData, data, "network", "interfaces", "macs", mac);
} else {
return;
}
data.put("device-number", deviceNumber);
data.put("mac", nic.getMacAddress());
data.put("owner-id", formatId(idFormatter, Instance.class, nic.getInstanceId()));
// TODO don't have a security group concept yet
data.put("security-group-ids", "");
data.put("security-groups", "");
data.put("vpc-id", formatId(idFormatter, Network.class, network.getId()));
data.put("vpc-ipv4-cidr-block", DataAccessor.fieldString(network, NetworkConstants.FIELD_CIDR));
if (deviceNumber != null && deviceNumber.intValue() == 0) {
instanceData.put("mac", nic.getMacAddress());
setNestedValue(instanceData, HostnameGenerator.getServicesDomain(network), "services", "domain");
}
}
protected String getZone(IdFormatter idFormatter, Instance instance) {
Zone zone = metadataDao.getZone(instance);
if (zone == null) {
return "unknown";
}
String name = zone.getName();
return name == null ? formatId(idFormatter, zone) : name;
}
protected String getInstanceType(Instance instance) {
Long mem = instance.getMemoryMb();
if (mem == null) {
mem = 64L;
}
Long cpu = DataAccessor.fieldLong(instance, InstanceConstants.FIELD_VCPU);
if (cpu == null) {
cpu = 1L;
}
StringBuilder buffer = new StringBuilder();
if (mem < 1024) {
buffer.append(mem).append("mb-");
} else {
buffer.append(mem.floatValue() / 1024).append("gb-");
}
buffer.append(cpu).append("cpu");
return buffer.toString();
}
protected String formatId(IdFormatter idFormatter, Class<?> clz, Long id) {
if (id == null) {
return null;
}
String type = objectManager.getType(clz);
return idFormatter.formatId(type, id).toString();
}
protected String formatId(IdFormatter idFormatter, Object obj) {
if (obj == null) {
return null;
}
String type = objectManager.getType(obj);
return idFormatter.formatId(type, ObjectUtils.getId(obj)).toString();
}
}