package com.sequenceiq.cloudbreak.cloud.azure;
import static com.sequenceiq.cloudbreak.cloud.azure.AzureStorage.IMAGES;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.microsoft.azure.AzureEnvironment;
import com.microsoft.azure.credentials.ApplicationTokenCredentials;
import com.microsoft.azure.management.datalake.store.DataLakeStoreAccountManagementClient;
import com.microsoft.azure.management.datalake.store.implementation.DataLakeStoreAccountManagementClientImpl;
import com.microsoft.azure.management.datalake.store.models.DataLakeStoreAccount;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CopyState;
import com.microsoft.azure.storage.blob.CopyStatus;
import com.microsoft.azure.storage.blob.ListBlobItem;
import com.sequenceiq.cloudbreak.api.model.AdlsFileSystemConfiguration;
import com.sequenceiq.cloudbreak.api.model.FileSystemConfiguration;
import com.sequenceiq.cloudbreak.api.model.FileSystemType;
import com.sequenceiq.cloudbreak.api.model.WasbFileSystemConfiguration;
import com.sequenceiq.cloudbreak.cloud.Setup;
import com.sequenceiq.cloudbreak.cloud.azure.client.AzureClient;
import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext;
import com.sequenceiq.cloudbreak.cloud.exception.CloudConnectorException;
import com.sequenceiq.cloudbreak.cloud.azure.view.AzureCredentialView;
import com.sequenceiq.cloudbreak.cloud.model.CloudCredential;
import com.sequenceiq.cloudbreak.cloud.model.CloudResource;
import com.sequenceiq.cloudbreak.cloud.model.CloudStack;
import com.sequenceiq.cloudbreak.cloud.model.FileSystem;
import com.sequenceiq.cloudbreak.cloud.model.Image;
import com.sequenceiq.cloudbreak.cloud.notification.PersistenceNotifier;
import com.sequenceiq.cloudbreak.common.type.ImageStatus;
import com.sequenceiq.cloudbreak.common.type.ImageStatusResult;
import com.sequenceiq.cloudbreak.common.type.ResourceType;
@Component
public class AzureSetup implements Setup {
private static final Logger LOGGER = LoggerFactory.getLogger(AzureSetup.class);
private static final String TEST_CONTAINER = "cb-test-container";
@Inject
private AzureUtils azureUtils;
@Inject
private AzureStorage armStorage;
@Override
public void prepareImage(AuthenticatedContext ac, CloudStack stack, Image image) {
LOGGER.info("prepare image: {}", image);
AzureCredentialView acv = new AzureCredentialView(ac.getCloudCredential());
String imageStorageName = armStorage.getImageStorageName(acv, ac.getCloudContext(), armStorage.getPersistentStorageName(stack.getParameters()),
armStorage.getArmAttachedStorageOption(stack.getParameters()));
String resourceGroupName = azureUtils.getResourceGroupName(ac.getCloudContext());
String imageResourceGroupName = armStorage.getImageResourceGroupName(ac.getCloudContext(), stack.getParameters());
String region = ac.getCloudContext().getLocation().getRegion().value();
AzureClient client = ac.getParameter(AzureClient.class);
try {
if (!client.resourceGroupExists(resourceGroupName)) {
client.createResourceGroup(resourceGroupName, region);
}
if (!client.resourceGroupExists(imageResourceGroupName)) {
client.createResourceGroup(imageResourceGroupName, region);
}
armStorage.createStorage(ac, client, imageStorageName, AzureDiskType.LOCALLY_REDUNDANT, imageResourceGroupName, region);
client.createContainerInStorage(imageResourceGroupName, imageStorageName, IMAGES);
if (!storageContainsImage(client, imageResourceGroupName, imageStorageName, image.getImageName())) {
client.copyImageBlobInStorageContainer(imageResourceGroupName, imageStorageName, IMAGES, image.getImageName());
}
} catch (Exception ex) {
LOGGER.error("Could not create image with the specified parameters: {}", ex);
throw new CloudConnectorException("Image creation failed because " + image.getImageName() + "does not exist or Cloudbreak could not reach.");
}
LOGGER.debug("prepare image has been executed");
}
@Override
public ImageStatusResult checkImageStatus(AuthenticatedContext ac, CloudStack stack, Image image) {
AzureCredentialView acv = new AzureCredentialView(ac.getCloudCredential());
String imageStorageName = armStorage.getImageStorageName(acv, ac.getCloudContext(), armStorage.getPersistentStorageName(stack.getParameters()),
armStorage.getArmAttachedStorageOption(stack.getParameters()));
String imageResourceGroupName = armStorage.getImageResourceGroupName(ac.getCloudContext(), stack.getParameters());
AzureCredentialView armCredentialView = new AzureCredentialView(ac.getCloudCredential());
try {
AzureClient client = ac.getParameter(AzureClient.class);
CopyState copyState = client.getCopyStatus(imageResourceGroupName, imageStorageName, IMAGES, image.getImageName());
if (CopyStatus.SUCCESS.equals(copyState.getStatus())) {
return new ImageStatusResult(ImageStatus.CREATE_FINISHED, ImageStatusResult.COMPLETED);
} else if (CopyStatus.ABORTED.equals(copyState.getStatus()) || CopyStatus.INVALID.equals(copyState.getStatus())) {
return new ImageStatusResult(ImageStatus.CREATE_FAILED, 0);
} else {
int percentage = (int) (((double) copyState.getBytesCopied() * ImageStatusResult.COMPLETED) / (double) copyState.getTotalBytes());
LOGGER.info(String.format("CopyStatus Pending %s byte/%s byte: %.4s %%", copyState.getTotalBytes(), copyState.getBytesCopied(), percentage));
return new ImageStatusResult(ImageStatus.IN_PROGRESS, percentage);
}
} catch (Exception ex) {
return new ImageStatusResult(ImageStatus.IN_PROGRESS, ImageStatusResult.HALF);
}
}
@Override
public void prerequisites(AuthenticatedContext ac, CloudStack stack, PersistenceNotifier persistenceNotifier) {
String storageGroup = azureUtils.getResourceGroupName(ac.getCloudContext());
CloudResource cloudResource = new CloudResource.Builder().type(ResourceType.ARM_TEMPLATE).name(storageGroup).build();
String region = ac.getCloudContext().getLocation().getRegion().value();
try {
AzureClient client = ac.getParameter(AzureClient.class);
persistenceNotifier.notifyAllocation(cloudResource, ac.getCloudContext());
if (!client.resourceGroupExists(storageGroup)) {
client.createResourceGroup(storageGroup, region);
}
} catch (Exception ex) {
throw new CloudConnectorException(ex);
}
LOGGER.debug("setup has been executed");
}
@Override
public void validateFileSystem(CloudCredential credential, FileSystem fileSystem) throws Exception {
String fileSystemType = fileSystem.getType();
if (fileSystemType.equalsIgnoreCase(FileSystemType.ADLS.name())) {
validateAdlsFileSystem(credential, fileSystem);
} else {
validateWasbFileSystem(fileSystem, fileSystemType);
}
}
private void validateWasbFileSystem(FileSystem fileSystem, String fileSystemType) throws URISyntaxException, InvalidKeyException, StorageException {
String accountName = fileSystem.getParameter(WasbFileSystemConfiguration.ACCOUNT_NAME, String.class);
String accountKey = fileSystem.getParameter(WasbFileSystemConfiguration.ACCOUNT_KEY, String.class);
String connectionString = "DefaultEndpointsProtocol=https;AccountName=" + accountName + ";AccountKey=" + accountKey;
CloudStorageAccount storageAccount = CloudStorageAccount.parse(connectionString);
CloudBlobClient blobClient = storageAccount.createCloudBlobClient();
CloudBlobContainer containerReference = blobClient.getContainerReference(TEST_CONTAINER + System.nanoTime());
try {
containerReference.createIfNotExists();
containerReference.delete();
} catch (StorageException e) {
if (e.getCause() instanceof UnknownHostException) {
throw new CloudConnectorException("The provided account does not belong to a valid storage account");
}
}
}
private void validateAdlsFileSystem(CloudCredential credential, FileSystem fileSystem) throws Exception {
Map<String, Object> credentialAttributes = credential.getParameters();
String clientSecret = String.valueOf(credentialAttributes.get(AdlsFileSystemConfiguration.CREDENTIAL_SECRET_KEY));
String subscriptionId = String.valueOf(credentialAttributes.get(AdlsFileSystemConfiguration.SUBSCRIPTION_ID));
String clientId = fileSystem.getStringParameter(AdlsFileSystemConfiguration.CLIENT_ID);
String tenantId = fileSystem.getStringParameter(AdlsFileSystemConfiguration.TENANT_ID);
String accountName = fileSystem.getStringParameter(FileSystemConfiguration.ACCOUNT_NAME);
ApplicationTokenCredentials creds = new ApplicationTokenCredentials(clientId, tenantId, clientSecret, AzureEnvironment.AZURE);
DataLakeStoreAccountManagementClient adlsClient = new DataLakeStoreAccountManagementClientImpl(creds);
adlsClient.withSubscriptionId(subscriptionId);
List<DataLakeStoreAccount> dataLakeStoreAccounts = adlsClient.accounts().list();
boolean validAccountname = false;
for (DataLakeStoreAccount account : dataLakeStoreAccounts) {
if (account.name().equalsIgnoreCase(accountName)) {
validAccountname = true;
break;
}
}
if (!validAccountname) {
throw new CloudConnectorException("The provided file system account name does not belong to a valid ADLS account");
}
}
private boolean storageContainsImage(AzureClient client, String groupName, String storageName, String image) throws Exception {
List<ListBlobItem> listBlobItems = client.listBlobInStorage(groupName, storageName, IMAGES);
for (ListBlobItem listBlobItem : listBlobItems) {
if (getNameFromConnectionString(listBlobItem.getUri().getPath()).equals(image.split("/")[image.split("/").length - 1])) {
return true;
}
}
return false;
}
private String getNameFromConnectionString(String connection) {
return connection.split("/")[connection.split("/").length - 1];
}
}