/******************************************************************************
* Copyright (c) 2012 GigaSpaces Technologies Ltd. All rights reserved *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
******************************************************************************/
package org.cloudifysource.esc.driver.provisioning.azure.client;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.esc.driver.provisioning.azure.model.AddressSpace;
import org.cloudifysource.esc.driver.provisioning.azure.model.AffinityGroups;
import org.cloudifysource.esc.driver.provisioning.azure.model.AttachedTo;
import org.cloudifysource.esc.driver.provisioning.azure.model.ConfigurationSet;
import org.cloudifysource.esc.driver.provisioning.azure.model.ConfigurationSets;
import org.cloudifysource.esc.driver.provisioning.azure.model.CreateAffinityGroup;
import org.cloudifysource.esc.driver.provisioning.azure.model.CreateHostedService;
import org.cloudifysource.esc.driver.provisioning.azure.model.CreateStorageServiceInput;
import org.cloudifysource.esc.driver.provisioning.azure.model.Deployment;
import org.cloudifysource.esc.driver.provisioning.azure.model.Deployments;
import org.cloudifysource.esc.driver.provisioning.azure.model.Disk;
import org.cloudifysource.esc.driver.provisioning.azure.model.Disks;
import org.cloudifysource.esc.driver.provisioning.azure.model.Error;
import org.cloudifysource.esc.driver.provisioning.azure.model.GlobalNetworkConfiguration;
import org.cloudifysource.esc.driver.provisioning.azure.model.HostedService;
import org.cloudifysource.esc.driver.provisioning.azure.model.HostedServices;
import org.cloudifysource.esc.driver.provisioning.azure.model.NetworkConfigurationSet;
import org.cloudifysource.esc.driver.provisioning.azure.model.Operation;
import org.cloudifysource.esc.driver.provisioning.azure.model.StorageServices;
import org.cloudifysource.esc.driver.provisioning.azure.model.VirtualNetworkSite;
import org.cloudifysource.esc.driver.provisioning.azure.model.VirtualNetworkSites;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.sun.jersey.client.urlconnection.HTTPSProperties;
/********************************************************************************
* A REST client implementation for the Azure REST API. this client is designed
* for using azure infrastructure as an IaaS. each VM is provisioned onto a
* separate cloud service that belong to the same virtual network site. this way
* all VM's are assigned public and private IP. and all VM's can be either a
* back end of a front end of you application. authentication is achieved by
* using self-signed certificates (OpenSSL, makecert)
*
* @author elip
********************************************************************************/
public class MicrosoftAzureRestClient {
private static final int HTTP_NOT_FOUND = 404;
private static final int HTTP_OK = 200;
private static final int HTTP_CREATED = 201;
private static final int HTTP_ACCEPTED = 202;
private static final char BAD_CHAR = 65279;
private String affinityPrefix;
private String cloudServicePrefix;
private String storagePrefix;
private Lock pendingRequest = new ReentrantLock(true);
private MicrosoftAzureRequestBodyBuilder requestBodyBuilder;
// Azure Management Service API End Point
private static final String CORE_MANAGEMENT_END_POINT = "https://management.core.windows.net/";
// Header names and values
private static final String X_MS_VERSION_HEADER_NAME = "x-ms-version";
private static final String X_MS_VERSION_HEADER_VALUE = "2013-03-01";
private static final String CONTENT_TYPE_HEADER_NAME = "Content-Type";
private static final String CONTENT_TYPE_HEADER_VALUE = "application/xml";
private static final String FAILED = "Failed";
private static final String SUCCEEDED = "Succeeded";
private static final String IN_PROGRESS = "InProgress";
private static final int MAX_RETRIES = 5;
private static final long DEFAULT_POLLING_INTERVAL = 5 * 1000; // 5 seconds
private static final long ESTIMATED_TIME_TO_START_VM = 5 * 60 * 1000; // 5
// minutes
private WebResource resource;
private Client client;
private String subscriptionId;
private MicrosoftAzureSSLHelper sslHelper;
private Logger logger = Logger.getLogger(this.getClass().getName());
public MicrosoftAzureRestClient(final String subscriptionId,
final String pathToPfx, final String pfxPassword,
final String affinityPrefix, final String cloudServicePrefix,
final String storagePrefix) {
this.subscriptionId = subscriptionId;
this.affinityPrefix = affinityPrefix;
this.cloudServicePrefix = cloudServicePrefix;
this.storagePrefix = storagePrefix;
this.init(pathToPfx, pfxPassword, affinityPrefix, cloudServicePrefix,
storagePrefix);
}
public MicrosoftAzureRestClient() {
}
public String getSubscriptionId() {
return subscriptionId;
}
public void setSubscriptionId(final String subscriptionId) {
this.subscriptionId = subscriptionId;
}
public String getAffinityPrefix() {
return affinityPrefix;
}
public void setAffinityPrefix(final String affinityPrefix) {
this.affinityPrefix = affinityPrefix;
}
public String getCloudServicePrefix() {
return cloudServicePrefix;
}
public void setCloudServicePrefix(final String cloudServicePrefix) {
this.cloudServicePrefix = cloudServicePrefix;
}
public String getStoragePrefix() {
return storagePrefix;
}
public void setStoragePrefix(final String storagePrefix) {
this.storagePrefix = storagePrefix;
}
/**
*
* @param logger
* - the logger to add to the client
*/
public void setLoggingFilter(final Logger logger) {
this.client.addFilter(new LoggingFilter(logger));
}
private void init(final String pathToPfx, final String pfxPassword,
final String affinityPrefix, final String cloudServicePrefix,
final String storagePrefix) {
try {
this.sslHelper = new MicrosoftAzureSSLHelper(pathToPfx, pfxPassword);
this.client = createClient(sslHelper.createSSLContext());
this.resource = client.resource(CORE_MANAGEMENT_END_POINT);
this.requestBodyBuilder = new MicrosoftAzureRequestBodyBuilder(
affinityPrefix, cloudServicePrefix, storagePrefix);
} catch (final Exception e) {
throw new RuntimeException("Failed initializing rest client : " + e.getMessage() , e);
}
}
private Client createClient(final SSLContext context) {
ClientConfig config = new DefaultClientConfig();
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
new HTTPSProperties(null, context));
Client httpClient = Client.create(config);
httpClient.setConnectTimeout(CloudifyConstants.DEFAULT_HTTP_CONNECTION_TIMEOUT);
httpClient.setReadTimeout(CloudifyConstants.DEFAULT_HTTP_READ_TIMEOUT);
return httpClient;
}
/**
*
* @param affinityGroup
* - the affinity group for the cloud service.
* @param endTime
* .
*
* @return - the newly created cloud service name.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public String createCloudService(final String affinityGroup,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
logger.fine(getThreadIdentity() + "Creating cloud service");
CreateHostedService createHostedService = requestBodyBuilder
.buildCreateCloudService(affinityGroup);
String serviceName = null;
try {
String xmlRequest = MicrosoftAzureModelUtils.marshall(
createHostedService, false);
ClientResponse response = doPost("/services/hostedservices",
xmlRequest);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
serviceName = createHostedService.getServiceName();
logger.info("Cloud service created : " + serviceName);
} catch (final Exception e) {
logger.warning("Failed to create cloud service : " + e.getMessage());
if (e instanceof MicrosoftAzureException) {
throw (MicrosoftAzureException)e;
}
if (e instanceof TimeoutException) {
throw (TimeoutException)e;
}
if (e instanceof InterruptedException) {
throw (InterruptedException)e;
}
}
return serviceName;
}
/**
* this method creates a storage account with the given name, or does
* nothing if the account exists.
*
* @param affinityGroup
* - the affinity group for the storage account.
* @param storageAccountName
* - the name for the storage account to create.
* @param endTime
* .
*
* @throws InterruptedException .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public void createStorageAccount(final String affinityGroup,
final String storageAccountName, final long endTime)
throws MicrosoftAzureException, TimeoutException,
InterruptedException {
CreateStorageServiceInput createStorageServiceInput = requestBodyBuilder
.buildCreateStorageAccount(affinityGroup, storageAccountName);
if (storageExists(storageAccountName)) {
logger.info("Using an already existing storage account : "
+ storageAccountName);
return;
}
logger.info("Creating a storage account : " + storageAccountName);
String xmlRequest = MicrosoftAzureModelUtils.marshall(
createStorageServiceInput, false);
ClientResponse response = doPost("/services/storageservices",
xmlRequest);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
logger.fine("Created a storage account : " + storageAccountName);
}
/**
* this method creates a virtual network with the given name, or does
* nothing if the network exists.
*
* @param addressSpace
* - CIDR notation specifying the address space for the virtual
* network.
* @param affinityGroup
* - the affinity group for this virtual network
* @param networkSiteName
* - the name for the network to create
* @param endTime
* .
*
* @throws InterruptedException .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public void createVirtualNetworkSite(final String addressSpace,
final String affinityGroup, final String networkSiteName,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
VirtualNetworkSites virtualNetworkSites = listVirtualNetworkSites();
if (virtualNetworkSites != null
&& virtualNetworkSites.contains(networkSiteName)) {
logger.info("Using an already existing virtual netowrk site : "
+ networkSiteName);
return;
} else {
if (virtualNetworkSites == null) {
virtualNetworkSites = new VirtualNetworkSites();
}
}
logger.info("Creating virtual network site : " + networkSiteName);
VirtualNetworkSite newSite = new VirtualNetworkSite();
AddressSpace address = new AddressSpace();
address.setAddressPrefix(addressSpace);
newSite.setAddressSpace(address);
newSite.setAffinityGroup(affinityGroup);
newSite.setName(networkSiteName);
virtualNetworkSites.getVirtualNetworkSites().add(newSite);
setNetworkConfiguration(endTime, virtualNetworkSites);
logger.fine("Created virtual network site : " + networkSiteName);
}
/**
* this method creates an affinity group with the given name, or does
* nothing if the group exists.
*
* @param affinityGroup
* - the name of the affinity group to create
* @param location
* - one of MS Data Centers locations.
* @param endTime
* .
*
* @throws InterruptedException .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public void createAffinityGroup(final String affinityGroup,
final String location, final long endTime)
throws MicrosoftAzureException, TimeoutException,
InterruptedException {
CreateAffinityGroup createAffinityGroup = requestBodyBuilder
.buildCreateAffinity(affinityGroup, location);
if (affinityExists(affinityGroup)) {
logger.info("Using an already existing affinity group : " + affinityGroup);
return;
}
logger.info("Creating affinity group : " + affinityGroup);
String xmlRequest = MicrosoftAzureModelUtils.marshall(
createAffinityGroup, false);
ClientResponse response = doPost("/affinitygroups", xmlRequest);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
logger.fine("Created affinity group : " + affinityGroup);
}
/**
* This method creates a virtual machine and a corresponding cloud service.
* the cloud service will use the affinity group specified by deploymentDesc.getAffinityGroup();
* If another request was made this method will wait until the pending request is finished.
*
* If a failure happened after the cloud service was created, this method will delete it and throw.
*
* @param deplyomentDesc
* .
* @param endTime
* .
* @return an instance of {@link RoleDetails} containing the ip addresses
* information for the created role.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public RoleDetails createVirtualMachineDeployment(
final CreatePersistentVMRoleDeploymentDescriptor deplyomentDesc,
final boolean isWindows,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
long currentTimeInMillis = System.currentTimeMillis();
long lockTimeout = endTime - currentTimeInMillis
- ESTIMATED_TIME_TO_START_VM;
if (lockTimeout < 0) {
throw new MicrosoftAzureException(
"Aborted request to provision virtual machine. "
+ "The timeout is less then the estimated time to provision the machine");
}
logger.fine(getThreadIdentity() + "Waiting for pending request lock for lock " + pendingRequest.hashCode());
boolean lockAcquired = pendingRequest.tryLock(lockTimeout,
TimeUnit.MILLISECONDS);
String serviceName = null;
Deployment deployment;
if (lockAcquired) {
logger.fine(getThreadIdentity() + "Lock acquired : " + pendingRequest.hashCode());
logger.fine(getThreadIdentity() + "Executing a request to provision a new virtual machine");
try {
serviceName = createCloudService(
deplyomentDesc.getAffinityGroup(), endTime);
deplyomentDesc.setHostedServiceName(serviceName);
deplyomentDesc.setDeploymentName(serviceName);
deployment = requestBodyBuilder.buildDeployment(deplyomentDesc,isWindows);
String xmlRequest = MicrosoftAzureModelUtils.marshall(
deployment, false);
logger.fine(getThreadIdentity() + "Launching virtual machine : "
+ deplyomentDesc.getRoleName());
ClientResponse response = doPost("/services/hostedservices/"
+ serviceName + "/deployments", xmlRequest);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
logger.fine(getThreadIdentity() + "About to release lock " + pendingRequest.hashCode());
pendingRequest.unlock();
} catch (final Exception e) {
logger.log(Level.FINE, getThreadIdentity() + "A failure occured : about to release lock "
+ pendingRequest.hashCode(), e);
if (serviceName != null) {
try {
// delete the dedicated cloud service that was created for the virtual machine.
deleteCloudService(serviceName, endTime);
} catch (final Exception e1) {
logger.warning("Failed deleting cloud service " + serviceName + " : " + e1.getMessage());
logger.finest(ExceptionUtils.getFullStackTrace(e1));
}
}
pendingRequest.unlock();
if (e instanceof MicrosoftAzureException) {
throw (MicrosoftAzureException)e;
}
if (e instanceof TimeoutException) {
throw (TimeoutException)e;
}
if (e instanceof InterruptedException) {
throw (InterruptedException)e;
}
throw new MicrosoftAzureException(e);
}
} else {
throw new TimeoutException(
"Failed to acquire lock for deleteDeployment request after + "
+ lockTimeout + " milliseconds");
}
Deployment deploymentResponse = null;
try {
deploymentResponse = waitForDeploymentStatus("Running",
serviceName, deployment.getDeploymentSlot(), endTime);
deploymentResponse = waitForRoleInstanceStatus("ReadyRole",
serviceName, deployment.getDeploymentSlot(), endTime);
} catch (final Exception e) {
logger.fine("Error while waiting for VM status : " + e.getMessage());
// the VM was created but with a bad status
deleteVirtualMachineByDeploymentName(serviceName, deployment.getName(), endTime);
if (e instanceof MicrosoftAzureException) {
throw (MicrosoftAzureException) e;
}
if (e instanceof TimeoutException) {
throw (TimeoutException) e;
}
if (e instanceof InterruptedException) {
throw (InterruptedException) e;
}
throw new MicrosoftAzureException(e);
}
RoleDetails roleAddressDetails = new RoleDetails();
roleAddressDetails.setId(deploymentResponse.getPrivateId());
roleAddressDetails
.setPrivateIp(deploymentResponse.getRoleInstanceList()
.getRoleInstances().get(0).getIpAddress());
ConfigurationSets configurationSets = deploymentResponse.getRoleList()
.getRoles().get(0).getConfigurationSets();
String publicIp = null;
for (ConfigurationSet configurationSet : configurationSets) {
if (configurationSet instanceof NetworkConfigurationSet) {
NetworkConfigurationSet networkConfigurationSet = (NetworkConfigurationSet) configurationSet;
publicIp = networkConfigurationSet.getInputEndpoints()
.getInputEndpoints().get(0).getvIp();
}
}
roleAddressDetails.setPublicIp(publicIp);
return roleAddressDetails;
}
/**
* @param vmStatus
*/
private boolean checkVirtualMachineStatusForError(final String vmStatus) {
return (vmStatus.equals("FailedStartingRole")
|| vmStatus.equals("FailedStartingVM")
|| vmStatus.equals("UnresponsiveRole")
|| vmStatus.equals("CyclingRole"));
}
/**
*
* @return - the response body listing every available OS Image that belongs
* to the subscription
* @throws MicrosoftAzureException
* - indicates an exception was caught during the API call
* @throws TimeoutException .
*/
public String listOsImages() throws MicrosoftAzureException,
TimeoutException {
ClientResponse response = doGet("/services/images");
checkForError(response);
return response.getEntity(String.class);
}
/**
* This method deletes the storage account with the specified name. or does
* nothing if the storage account does not exist.
* @param storageAccountName
* .
* @param endTime
* .
*
* @return - true if the operation was successful, throws otherwise.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public boolean deleteStorageAccount(final String storageAccountName,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
if (!storageExists(storageAccountName)) {
return true;
}
logger.info("Deleting storage account : " + storageAccountName);
ClientResponse response = doDelete("/services/storageservices/"
+ storageAccountName);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
logger.fine("Deleted storage account : " + storageAccountName);
return true;
}
/**
* This method deletes the affinity group with the specified name. or does
* nothing if the affinity group does not exist.
* @param affinityGroupName
* .
* @param endTime
* .
* @return true if the operation was successful, throws otherwise.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public boolean deleteAffinityGroup(final String affinityGroupName,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
if (!affinityExists(affinityGroupName)) {
return true;
}
logger.info("Deleting affinity group : " + affinityGroupName);
ClientResponse response = doDelete("/affinitygroups/"
+ affinityGroupName);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
logger.fine("Deleted affinity group : " + affinityGroupName);
return true;
}
/**
* This method deletes the cloud service with the specified name. or does
* nothing if the cloud service does not exist.
* @param cloudServiceName
* .
* @param endTime
* .
* @return - true if the operation was successful, throws otherwise.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public boolean deleteCloudService(final String cloudServiceName,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
if (!cloudServiceExists(cloudServiceName)) {
logger.info("Cloud service " + cloudServiceName + " does not exist.");
return true;
}
logger.fine("Deleting cloud service : " + cloudServiceName);
ClientResponse response = doDelete("/services/hostedservices/"
+ cloudServiceName);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
return true;
}
/**
*
* @param machineIp
* - the machine ip.
* @param isPrivateIp
* - whether or not this ip is private or public.
* @param endTime
* .
* @throws TimeoutException .
* @throws MicrosoftAzureException .
* @throws InterruptedException .
*/
public void deleteVirtualMachineByIp(final String machineIp,
final boolean isPrivateIp, final long endTime)
throws TimeoutException, MicrosoftAzureException,
InterruptedException {
Deployment deployment = getDeploymentByIp(machineIp, isPrivateIp);
if (deployment == null) {
throw new MicrosoftAzureException("Could not find a Virtual Machine with IP " + machineIp);
}
logger.fine("Deployment name for Virtual Machine with IP " + machineIp + " is " + deployment.getName());
deleteVirtualMachineByDeploymentName(deployment.getHostedServiceName(),
deployment.getName(), endTime);
}
/**
* This method deletes the virtual machine under the deployment specifed by deploymentName.
* it also deletes the associated disk and cloud service.
* @param cloudServiceName
* .
* @param deploymentName
* .
* @param endTime
* .
* @throws TimeoutException .
* @throws MicrosoftAzureException .
* @throws InterruptedException .
*/
public void deleteVirtualMachineByDeploymentName(
final String cloudServiceName, final String deploymentName,
final long endTime) throws TimeoutException,
MicrosoftAzureException, InterruptedException {
String diskName = null;
String roleName = null;
Disk disk = getDiskByAttachedCloudService(cloudServiceName);
if (disk != null) {
diskName = disk.getName();
roleName = disk.getAttachedTo().getRoleName();
} else {
throw new IllegalStateException("Disk cannot be null for an existing deployment " + deploymentName
+ " in cloud service " + cloudServiceName);
}
logger.info("Deleting Virtual Machine " + roleName);
deleteDeployment(cloudServiceName, deploymentName,
endTime);
logger.fine("Deleting cloud service : " + cloudServiceName
+ " that was dedicated for virtual machine " + roleName);
deleteCloudService(cloudServiceName, endTime);
logger.fine("Waiting for OS Disk " + diskName + " to detach from role " + roleName);
waitForDiskToDetach(diskName, roleName, endTime);
logger.info("Deleting OS Disk : " + diskName);
deleteOSDisk(diskName, endTime);
}
private Disk getDiskByAttachedCloudService(final String cloudServiceName)
throws MicrosoftAzureException, TimeoutException {
Disks disks = listOSDisks();
for (Disk disk : disks) {
AttachedTo attachedTo = disk.getAttachedTo();
if ((attachedTo != null) && (attachedTo.getHostedServiceName().equals(cloudServiceName))) {
return disk;
}
}
return null;
}
private void waitForDiskToDetach(final String diskName, final String roleName, long endTime)
throws TimeoutException, MicrosoftAzureException, InterruptedException {
while (true) {
Disks disks = listOSDisks();
Disk osDisk = null;
for (Disk disk : disks) {
if (disk.getName().equals(diskName)) {
osDisk = disk;
break;
}
}
if (osDisk != null) {
if (osDisk.getAttachedTo() == null) {
return;
} else {
logger.fine("Disk " + diskName + " is still attached to role " + osDisk.getAttachedTo().getRoleName());
Thread.sleep(DEFAULT_POLLING_INTERVAL);
}
} else {
throw new MicrosoftAzureException("Disk " + diskName + " does not exist");
}
if (System.currentTimeMillis() > endTime) {
throw new TimeoutException(
"Timed out waiting for disk " + diskName + " to detach from role " + roleName);
}
}
}
/**
* this method return all disks that are currently being used by this
* subscription. NOTE : disks that are not attached to any deployment are
* also returned. this means that {@code Disk.getAttachedTo} might return
* null.
*
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public Disks listOSDisks() throws MicrosoftAzureException, TimeoutException {
ClientResponse response = doGet("/services/disks");
checkForError(response);
String responseBody = response.getEntity(String.class);
return (Disks) MicrosoftAzureModelUtils.unmarshall(responseBody);
}
/**
* This method deletes an OS disk with the specified name. or does
* nothing if the disk does not exist.
* @param diskName
* .
* @param endTime
* .
* @return - true if the operation was successful, throws otherwise.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public boolean deleteOSDisk(final String diskName, final long endTime)
throws MicrosoftAzureException, TimeoutException,
InterruptedException {
if (!osDiskExists(diskName)) {
logger.info("OS Disk " + diskName + " does not exist");
return true;
}
ClientResponse response = doDelete("/services/disks/" + diskName);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
return true;
}
/**
* This method deletes just the virtual machine from the specified cloud service.
* associated OS Disk and cloud service are not removed.
* @param hostedServiceName
* .
* @param deploymentName
* .
* @param endTime
* .
* @return - true if the operation was successful, throws otherwise.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public boolean deleteDeployment(final String hostedServiceName,
final String deploymentName, final long endTime)
throws MicrosoftAzureException, TimeoutException,
InterruptedException {
if (!deploymentExists(hostedServiceName, deploymentName)) {
logger.info("Deployment " + deploymentName + " does not exist");
return true;
}
long currentTimeInMillis = System.currentTimeMillis();
long lockTimeout = endTime - currentTimeInMillis;
logger.fine(getThreadIdentity() + "Waiting for pending request lock...");
boolean lockAcquired = pendingRequest.tryLock(lockTimeout,
TimeUnit.MILLISECONDS);
if (lockAcquired) {
logger.fine(getThreadIdentity() + "Lock acquired : " + pendingRequest.hashCode());
logger.fine(getThreadIdentity() + "Executing a request to delete virtual machine");
try {
logger.fine(getThreadIdentity() + "Deleting deployment of virtual machine from : "
+ deploymentName);
ClientResponse response = doDelete("/services/hostedservices/"
+ hostedServiceName + "/deployments/" + deploymentName);
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
pendingRequest.unlock();
logger.fine(getThreadIdentity() + "Lock unlcoked");
} catch (final Exception e) {
logger.fine(getThreadIdentity() + "About to release lock " + pendingRequest.hashCode());
pendingRequest.unlock();
if (e instanceof MicrosoftAzureException) {
throw (MicrosoftAzureException)e;
}
if (e instanceof TimeoutException) {
throw (TimeoutException)e;
}
if (e instanceof InterruptedException) {
throw (InterruptedException)e;
}
}
return true;
} else {
throw new TimeoutException(
"Failed to acquire lock for deleteDeployment request after + "
+ lockTimeout + " milliseconds");
}
}
/**
*
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public HostedServices listHostedServices() throws MicrosoftAzureException,
TimeoutException {
ClientResponse response = doGet("/services/hostedservices");
String responseBody = response.getEntity(String.class);
checkForError(response);
return (HostedServices) MicrosoftAzureModelUtils
.unmarshall(responseBody);
}
/**
*
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public AffinityGroups listAffinityGroups() throws MicrosoftAzureException,
TimeoutException {
ClientResponse response = doGet("/affinitygroups");
checkForError(response);
String responseBody = response.getEntity(String.class);
checkForError(response);
return (AffinityGroups) MicrosoftAzureModelUtils
.unmarshall(responseBody);
}
/**
*
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public StorageServices listStorageServices()
throws MicrosoftAzureException, TimeoutException {
ClientResponse response = doGet("/services/storageservices");
String responseBody = response.getEntity(String.class);
return (StorageServices) MicrosoftAzureModelUtils
.unmarshall(responseBody);
}
/**
*
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public VirtualNetworkSites listVirtualNetworkSites()
throws MicrosoftAzureException, TimeoutException {
ClientResponse response = doGet("/services/networking/media");
if (response.getStatus() == HTTP_NOT_FOUND) {
return null;
}
String responseBody = response.getEntity(String.class);
if ( responseBody.charAt(0) == BAD_CHAR) {
responseBody = responseBody.substring(1);
}
GlobalNetworkConfiguration globalNetowrkConfiguration = (GlobalNetworkConfiguration) MicrosoftAzureModelUtils
.unmarshall(responseBody);
return globalNetowrkConfiguration.getVirtualNetworkConfiguration()
.getVirtualNetworkSites();
}
/**
* @param hostedServiceName
* - hosted service name.
* @param embedDeployments
* - whether or not to include the deployments of this hosted
* service in the response.
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public HostedService getHostedService(final String hostedServiceName,
final boolean embedDeployments) throws MicrosoftAzureException,
TimeoutException {
StringBuilder builder = new StringBuilder();
builder.append("/services/hostedservices/").append(hostedServiceName);
if (embedDeployments) {
builder.append("?embed-detail=true");
}
ClientResponse response = doGet(builder.toString());
checkForError(response);
String responseBody = response.getEntity(String.class);
return (HostedService) MicrosoftAzureModelUtils
.unmarshall(responseBody);
}
/**
*
* @param hostedServiceName
* .
* @param deploymentSlot
* .
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public Deployment getDeploymentByDeploymentSlot(
final String hostedServiceName, final String deploymentSlot)
throws MicrosoftAzureException, TimeoutException {
ClientResponse response = null;
try {
response = doGet("/services/hostedservices/" + hostedServiceName
+ "/deploymentslots/" + deploymentSlot);
checkForError(response);
} catch (TimeoutException e) {
logger.warning("Timed out while waiting for deployment details. This may cause a leaking node");
throw e;
}
String responseBody = response.getEntity(String.class);
return (Deployment) MicrosoftAzureModelUtils
.unmarshall(responseBody);
}
/**
*
* @param hostedServiceName
* .
* @param deploymentName
* .
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public Deployment getDeploymentByDeploymentName(
final String hostedServiceName, final String deploymentName)
throws MicrosoftAzureException, TimeoutException {
ClientResponse response = null;
try {
response = doGet("/services/hostedservices/" + hostedServiceName
+ "/deployments/" + deploymentName);
checkForError(response);
} catch (TimeoutException e) {
logger.warning("Timed out while waiting for deployment details. this may cause a leaking node");
throw e;
}
String responseBody = response.getEntity(String.class);
Deployment deployment = (Deployment) MicrosoftAzureModelUtils
.unmarshall(responseBody);
deployment.setHostedServiceName(hostedServiceName);
return deployment;
}
/**
*
* @param machineIp
* .
* @param isPrivateIp
* .
* @return .
* @throws MicrosoftAzureException .
* @throws TimeoutException .
*/
public Deployment getDeploymentByIp(final String machineIp,
final boolean isPrivateIp) throws MicrosoftAzureException,
TimeoutException {
Deployment deployment = null;
HostedServices cloudServices = listHostedServices();
for (HostedService hostedService : cloudServices) {
String cloudServiceName = hostedService.getServiceName();
Deployments deployments = getHostedService(cloudServiceName, true)
.getDeployments();
// skip empty cloud services
if (!deployments.getDeployments().isEmpty()) {
deployment = deployments.getDeployments().get(0);
String deploymentName = deployment.getName();
deployment = getDeploymentByDeploymentName(cloudServiceName,
deploymentName);
String publicIp = getPublicIpFromDeployment(deployment);
String privateIp = getPrivateIpFromDeployment(deployment);
String ip = isPrivateIp ? privateIp : publicIp;
if (machineIp.equals(ip)) {
deployment.setHostedServiceName(cloudServiceName);
return deployment;
}
}
}
logger.info("Could not find any roles with ip :" + machineIp);
return null;
}
/**
* This method deletes the virtual network specified. or does
* nothing if the virtual network does not exist.
* @param virtualNetworkSite
* - virtual network site name to delete .
* @param endTime
* .
* @return - true if the operation was successful, throws otherwise.
* @throws MicrosoftAzureException .
* @throws TimeoutException .
* @throws InterruptedException .
*/
public boolean deleteVirtualNetworkSite(final String virtualNetworkSite,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
if (!virtualNetworkExists(virtualNetworkSite)) {
return true;
}
VirtualNetworkSites virtualNetworkSites = listVirtualNetworkSites();
int index = 0;
for (int i = 0; i < virtualNetworkSites.getVirtualNetworkSites().size(); i++) {
VirtualNetworkSite site = virtualNetworkSites
.getVirtualNetworkSites().get(i);
if (site.getName().equals(virtualNetworkSite)) {
index = i;
break;
}
}
virtualNetworkSites.getVirtualNetworkSites().remove(index);
logger.info("Deleting virtual network site : " + virtualNetworkSite);
setNetworkConfiguration(endTime, virtualNetworkSites);
logger.fine("Deleted virtual network site : " + virtualNetworkSite);
return true;
}
private String extractRequestId(final ClientResponse response) {
return response.getHeaders().getFirst("x-ms-request-id");
}
private ClientResponse doPut(final String url, final String body,
final String contentType) throws MicrosoftAzureException {
ClientResponse response = resource.path(subscriptionId + url)
.header(X_MS_VERSION_HEADER_NAME, X_MS_VERSION_HEADER_VALUE)
.header(CONTENT_TYPE_HEADER_NAME, contentType)
.put(ClientResponse.class, body);
checkForError(response);
return response;
}
private ClientResponse doPost(final String url, final String body)
throws MicrosoftAzureException {
ClientResponse response = resource.path(subscriptionId + url)
.header(X_MS_VERSION_HEADER_NAME, X_MS_VERSION_HEADER_VALUE)
.header(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_VALUE)
.post(ClientResponse.class, body);
checkForError(response);
return response;
}
private ClientResponse doGet(final String url)
throws MicrosoftAzureException, TimeoutException {
ClientResponse response = null;
for (int i = 0; i < MAX_RETRIES; i++) {
try {
response = resource
.path(subscriptionId + url)
.header(X_MS_VERSION_HEADER_NAME,
X_MS_VERSION_HEADER_VALUE)
.header(CONTENT_TYPE_HEADER_NAME,
CONTENT_TYPE_HEADER_VALUE)
.get(ClientResponse.class);
break;
} catch (ClientHandlerException e) {
logger.warning("Caught an exception while executing GET with url "
+ url + ". Message :" + e.getMessage());
logger.warning("Retrying request");
continue;
}
}
if (response == null) {
throw new TimeoutException("Timed out while executing GET after "
+ MAX_RETRIES);
}
return response;
}
private ClientResponse doDelete(final String url)
throws MicrosoftAzureException {
ClientResponse response = resource.path(subscriptionId + url)
.header(X_MS_VERSION_HEADER_NAME, X_MS_VERSION_HEADER_VALUE)
.header(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_VALUE)
.delete(ClientResponse.class);
checkForError(response);
return response;
}
private boolean cloudServiceExists(final String cloudServiceName) throws MicrosoftAzureException, TimeoutException {
HostedServices cloudServices = listHostedServices();
return (cloudServices.contains(cloudServiceName));
}
private boolean affinityExists(final String affinityGroupName)
throws MicrosoftAzureException, TimeoutException {
AffinityGroups affinityGroups = listAffinityGroups();
return (affinityGroups.contains(affinityGroupName));
}
private boolean deploymentExists(final String cloudServiceName, final String deploymentName)
throws MicrosoftAzureException, TimeoutException {
HostedService service = getHostedService(cloudServiceName, true);
if ((service.getDeployments() != null) && (service.getDeployments().contains(deploymentName))) {
return true;
} else {
return false;
}
}
private boolean storageExists(final String storageAccouhtName)
throws MicrosoftAzureException, TimeoutException {
StorageServices storageServices = listStorageServices();
return (storageServices.contains(storageAccouhtName));
}
private boolean osDiskExists(final String osDiskName)
throws MicrosoftAzureException, TimeoutException {
Disks disks = listOSDisks();
return (disks.contains(osDiskName));
}
private boolean virtualNetworkExists(final String virtualNetworkName)
throws MicrosoftAzureException, TimeoutException {
VirtualNetworkSites sites = listVirtualNetworkSites();
return (sites.contains(virtualNetworkName));
}
private void checkForError(final ClientResponse response)
throws MicrosoftAzureException {
int status = response.getStatus();
if (status != HTTP_OK && status != HTTP_CREATED
&& status != HTTP_ACCEPTED) { // we got some
// sort of error
Error error = (Error) MicrosoftAzureModelUtils.unmarshall(response
.getEntity(String.class));
String errorMessage = error.getMessage();
String errorCode = error.getCode();
throw new MicrosoftAzureException(errorCode,
errorMessage);
}
}
private Deployment waitForDeploymentStatus(final String state,
final String hostedServiceName, final String deploymentSlot,
final long endTime) throws TimeoutException,
MicrosoftAzureException, InterruptedException {
while (true) {
Deployment deployment = getDeploymentByDeploymentSlot(
hostedServiceName, deploymentSlot);
String status = deployment.getStatus();
if (status.equals(state)) {
return deployment;
} else {
Thread.sleep(DEFAULT_POLLING_INTERVAL);
}
if (System.currentTimeMillis() > endTime) {
throw new TimeoutException(
"Timed out waiting for operation to finish. last state was : "
+ status);
}
}
}
private Deployment waitForRoleInstanceStatus(final String state,
final String hostedServiceName, final String deploymentSlot,
final long endTime) throws TimeoutException,
MicrosoftAzureException, InterruptedException {
while (true) {
Deployment deployment = getDeploymentByDeploymentSlot(
hostedServiceName, deploymentSlot);
String roleName = deployment.getRoleList().getRoles().get(0)
.getRoleName();
String status = deployment.getRoleInstanceList().getRoleInstances()
.get(0).getInstanceStatus();
boolean error = checkVirtualMachineStatusForError(status);
if (error) {
// bad status of VM.
throw new MicrosoftAzureException("Virtual Machine " + roleName
+ " was provisioned but found in status " + status);
}
if (status.equals(state)) {
return deployment;
} else {
Thread.sleep(DEFAULT_POLLING_INTERVAL);
}
if (System.currentTimeMillis() > endTime) {
throw new TimeoutException(
"Timed out waiting for operation to finish. last state was : "
+ status);
}
}
}
private void setNetworkConfiguration(final long endTime,
final VirtualNetworkSites virtualNetworkSites)
throws MicrosoftAzureException, TimeoutException,
InterruptedException {
GlobalNetworkConfiguration networkConfiguration = requestBodyBuilder
.buildGlobalNetworkConfiguration(virtualNetworkSites
.getVirtualNetworkSites());
String xmlRequest = MicrosoftAzureModelUtils.marshall(
networkConfiguration, true);
ClientResponse response = doPut("/services/networking/media",
xmlRequest, "text/plain");
String requestId = extractRequestId(response);
waitForRequestToFinish(requestId, endTime);
}
private void waitForRequestToFinish(final String requestId,
final long endTime) throws MicrosoftAzureException,
TimeoutException, InterruptedException {
while (true) {
// Query Azure for operation details
Operation operation = getOperation(requestId);
String status = operation.getStatus();
if (!status.equals(IN_PROGRESS)) {
// if operation succeeded, we are good to go
if (status.equals(SUCCEEDED)) {
return;
}
if (status.equals(FAILED)) {
String errorMessage = operation.getError().getMessage();
String errorCode = operation.getError().getCode();
throw new MicrosoftAzureException(errorCode, errorMessage);
}
} else {
Thread.sleep(DEFAULT_POLLING_INTERVAL);
}
if (System.currentTimeMillis() > endTime) {
throw new TimeoutException(
"Timed out waiting for operation to finish. last state was : "
+ status);
}
}
}
private Operation getOperation(final String requestId)
throws MicrosoftAzureException, TimeoutException {
ClientResponse response = doGet("/operations/" + requestId);
return (Operation) MicrosoftAzureModelUtils
.unmarshall(response.getEntity(String.class));
}
private String getPublicIpFromDeployment(final Deployment deployment) {
ConfigurationSets configurationSets = deployment.getRoleList()
.getRoles().get(0).getConfigurationSets();
String publicIp = null;
for (ConfigurationSet configurationSet : configurationSets) {
if (configurationSet instanceof NetworkConfigurationSet) {
NetworkConfigurationSet networkConfigurationSet = (NetworkConfigurationSet) configurationSet;
publicIp = networkConfigurationSet.getInputEndpoints()
.getInputEndpoints().get(0).getvIp();
}
}
return publicIp;
}
private String getPrivateIpFromDeployment(final Deployment deployment) {
return deployment.getRoleInstanceList().getRoleInstances().get(0)
.getIpAddress();
}
private String getThreadIdentity() {
String threadName = Thread.currentThread().getName();
long threadId = Thread.currentThread().getId();
return "[" + threadName + "]" + "[" + threadId + "] - ";
}
}