package com.sequenceiq.cloudbreak.cloud.azure;
import static org.apache.commons.lang3.StringUtils.isNoneEmpty;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.google.common.base.Splitter;
import com.microsoft.azure.management.network.NetworkSecurityGroup;
import com.microsoft.azure.management.network.NetworkSecurityRule;
import com.microsoft.azure.management.network.Subnet;
import com.microsoft.azure.management.resources.Deployment;
import com.microsoft.azure.management.resources.DeploymentOperation;
import com.microsoft.azure.management.resources.DeploymentOperations;
import com.sequenceiq.cloudbreak.cloud.azure.client.AzureClient;
import com.sequenceiq.cloudbreak.cloud.context.CloudContext;
import com.sequenceiq.cloudbreak.cloud.exception.CloudConnectorException;
import com.sequenceiq.cloudbreak.cloud.azure.status.AzureStackStatus;
import com.sequenceiq.cloudbreak.cloud.model.CloudResource;
import com.sequenceiq.cloudbreak.cloud.model.CloudResourceStatus;
import com.sequenceiq.cloudbreak.cloud.model.CloudStack;
import com.sequenceiq.cloudbreak.cloud.model.Group;
import com.sequenceiq.cloudbreak.cloud.model.InstanceTemplate;
import com.sequenceiq.cloudbreak.cloud.model.Network;
import com.sequenceiq.cloudbreak.cloud.model.ResourceStatus;
import com.sequenceiq.cloudbreak.common.type.ResourceType;
@Component
public class AzureUtils {
public static final int NOT_FOUND = 404;
private static final Logger LOGGER = LoggerFactory.getLogger(AzureUtils.class);
private static final String RG_NAME = "resourceGroupName";
private static final String SUBNET_ID = "subnetId";
private static final String NETWORK_ID = "networkId";
private static final String NO_PUBLIC_IP = "noPublicIp";
private static final String NO_FIREWALL_RULES = "noFirewallRules";
private static final int PORT_22 = 22;
private static final int PORT_443 = 443;
private static final int PORT_RANGE_NUM = 2;
private static final int RG_PART = 4;
private static final int ID_SEGMENTS = 9;
private static final int SEC_GROUP_PART = 8;
private static final int HOST_GROUP_LENGTH = 3;
@Value("${cb.max.azure.resource.name.length:}")
private int maxResourceNameLength;
public CloudResource getTemplateResource(List<CloudResource> resourceList) {
for (CloudResource resource : resourceList) {
if (resource.getType() == ResourceType.ARM_TEMPLATE) {
return resource;
}
}
throw new CloudConnectorException(String.format("No resource found: %s", ResourceType.ARM_TEMPLATE));
}
public static String getGroupName(String group) {
String shortened = WordUtils.initials(group.replaceAll("_", " ")).toLowerCase();
return shortened.length() <= HOST_GROUP_LENGTH ? shortened : shortened.substring(0, HOST_GROUP_LENGTH);
}
public static void removeBlankSpace(StringBuilder sb) {
int j = 0;
for (int i = 0; i < sb.length(); i++) {
if (!Character.isWhitespace(sb.charAt(i))) {
sb.setCharAt(j++, sb.charAt(i));
}
}
sb.delete(j, sb.length());
}
public String getLoadBalancerId(String stackName) {
return String.format("%s%s", stackName, "lb");
}
public String getPrivateInstanceId(String stackName, String groupName, String privateId) {
return String.format("%s%s%s", stackName, getGroupName(groupName), privateId);
}
public String getStackName(CloudContext cloudContext) {
return Splitter.fixedLength(maxResourceNameLength - cloudContext.getId().toString().length())
.splitToList(cloudContext.getName()).get(0) + cloudContext.getId();
}
public CloudResourceStatus getTemplateStatus(CloudResource resource, Deployment templateDeployment, AzureClient access, String stackName) {
String status = templateDeployment.provisioningState();
LOGGER.info("Azure stack status of: {} is: {}", resource.getName(), status);
ResourceStatus resourceStatus = AzureStackStatus.mapResourceStatus(status);
CloudResourceStatus armResourceStatus = null;
if (ResourceStatus.FAILED.equals(resourceStatus)) {
LOGGER.debug("Cloud resource status: {}", resourceStatus);
try {
// TODO: discuss with Doktorics why this is needed
DeploymentOperations templateDeploymentOperations = access.getTemplateDeploymentOperations(stackName, stackName);
for (DeploymentOperation deploymentOperation : templateDeploymentOperations.list()) {
if ("Failed".equals(deploymentOperation.provisioningState())) {
String statusMessage = (String) deploymentOperation.statusMessage();
armResourceStatus = new CloudResourceStatus(resource, AzureStackStatus.mapResourceStatus(status), statusMessage);
break;
}
}
} catch (Exception e) {
armResourceStatus = new CloudResourceStatus(resource, AzureStackStatus.mapResourceStatus(status), e.getMessage());
}
} else {
LOGGER.debug("Cloud resource status: {}", resourceStatus);
armResourceStatus = new CloudResourceStatus(resource, AzureStackStatus.mapResourceStatus(status));
}
return armResourceStatus;
}
public String getResourceGroupName(CloudContext cloudContext) {
return getStackName(cloudContext);
}
public boolean isExistingNetwork(Network network) {
return isNoneEmpty(getCustomNetworkId(network)) && isNoneEmpty(getCustomResourceGroupName(network)) && isNoneEmpty(getCustomSubnetId(network));
}
public boolean isPrivateIp(Network network) {
if (network.getParameters().containsKey(NO_PUBLIC_IP)) {
return network.getParameter(NO_PUBLIC_IP, Boolean.class);
} else {
return false;
}
}
public boolean isNoSecurityGroups(Network network) {
if (network.getParameters().containsKey(NO_FIREWALL_RULES)) {
return network.getParameter(NO_FIREWALL_RULES, Boolean.class);
} else {
return false;
}
}
public String getCustomNetworkId(Network network) {
return network.getStringParameter(NETWORK_ID);
}
public String getCustomResourceGroupName(Network network) {
return network.getStringParameter(RG_NAME);
}
public String getCustomSubnetId(Network network) {
return network.getStringParameter(SUBNET_ID);
}
public void validateSubnetRules(AzureClient client, Network network) {
if (isExistingNetwork(network)) {
String resourceGroupName = getCustomResourceGroupName(network);
String networkId = getCustomNetworkId(network);
String subnetId = getCustomSubnetId(network);
try {
Subnet subnet = client.getSubnetProperties(resourceGroupName, networkId, subnetId);
NetworkSecurityGroup networkSecurityGroup = subnet.getNetworkSecurityGroup();
if (networkSecurityGroup != null) {
validateSecurityGroup(client, networkSecurityGroup);
}
} catch (Exception e) {
throw new CloudConnectorException("Subnet validation failed, cause: " + e.getMessage(), e);
}
}
}
public void validateStorageType(CloudStack stack) {
for (Group group : stack.getGroups()) {
InstanceTemplate template = group.getReferenceInstanceConfiguration().getTemplate();
String flavor = template.getFlavor();
String volumeType = template.getVolumeType();
AzureDiskType diskType = AzureDiskType.getByValue(volumeType);
if (AzureDiskType.PREMIUM_LOCALLY_REDUNDANT.equals(diskType) && !flavor.contains("_DS")) {
throw new CloudConnectorException("Only the DS instance types supports the premium storage.");
}
}
}
private void validateSecurityGroup(AzureClient client, NetworkSecurityGroup networkSecurityGroup) {
String securityGroupId = networkSecurityGroup.id();
String[] parts = securityGroupId.split("/");
if (parts.length != ID_SEGMENTS) {
LOGGER.info("Cannot get the security group's properties, id: {}", securityGroupId);
return;
}
try {
NetworkSecurityGroup securityGroup = client.getSecurityGroupProperties(parts[RG_PART], parts[SEC_GROUP_PART]);
LOGGER.info("Retrieved security group properties: {}", securityGroup);
Map<String, NetworkSecurityRule> securityRules = securityGroup.securityRules();
boolean port22Found = false;
boolean port443Found = false;
for (NetworkSecurityRule securityRule : securityRules.values()) {
if (isValidInboundRule(securityRule)) {
String destinationPortRange = securityRule.destinationPortRange();
if ("*".equals(destinationPortRange)) {
return;
}
String[] range = destinationPortRange.split("-");
port443Found = port443Found || isPortFound(PORT_443, range);
port22Found = port22Found || isPortFound(PORT_22, range);
if (port22Found && port443Found) {
return;
}
}
}
} catch (Exception e) {
throw new CloudConnectorException("Validating security group failed.", e);
}
throw new CloudConnectorException("The specified subnet's security group does not allow traffic for port 22 and/or 443");
}
private boolean isValidInboundRule(NetworkSecurityRule securityRule) {
String protocol = securityRule.protocol().toString().toLowerCase();
String access = securityRule.access().toString().toLowerCase();
String direction = securityRule.direction().toString().toLowerCase();
return "inbound".equals(direction)
&& ("tcp".equals(protocol) || "*".equals(protocol))
&& "allow".equals(access);
}
private boolean isPortFound(int port, String[] destinationPortRange) {
if (destinationPortRange.length == PORT_RANGE_NUM) {
return isPortInRange(port, destinationPortRange);
}
return isPortMatch(port, destinationPortRange[0]);
}
private boolean isPortInRange(int port, String[] range) {
return Integer.parseInt(range[0]) <= port && Integer.parseInt(range[1]) >= port;
}
private boolean isPortMatch(int port, String destinationPortRange) {
return port == Integer.parseInt(destinationPortRange);
}
}