/**
* Abiquo community edition
* cloud management application for hybrid clouds
* Copyright (C) 2008-2010 - Abiquo Holdings S.L.
*
* This application is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC
* LICENSE as published by the Free Software Foundation under
* version 3 of the License
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* LESSER GENERAL PUBLIC LICENSE v.3 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package com.abiquo.vsm.client;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.codec.binary.Base64;
import org.apache.wink.client.ClientConfig;
import org.apache.wink.client.ClientResponse;
import org.apache.wink.client.Resource;
import org.apache.wink.client.RestClient;
import com.abiquo.vsm.model.transport.ErrorDto;
import com.abiquo.vsm.model.transport.PhysicalMachineDto;
import com.abiquo.vsm.model.transport.PhysicalMachinesDto;
import com.abiquo.vsm.model.transport.VirtualMachineDto;
import com.abiquo.vsm.model.transport.VirtualMachinesDto;
/**
* Client stub to connect to the VSM module.
*
* @author ibarrera
* @author enric.ruiz@abiquo.com
*/
public class VSMClient
{
/** The default context path of the VSM module. */
private static final String DEFAULT_CONTEXT_PATH = "vsm/api";
/** The REST client. */
private RestClient client;
/** The default path of the VSM module. */
private String basePath;
/**
* Initializes an existent instance for the given URI.
*
* @param uri The target URI.
*/
public VSMClient initialize(final String uri)
{
if (!isValidURI(uri))
{
throw new IllegalArgumentException("The provided parameter is not a valid URL.");
}
ClientConfig clientConfig = new ClientConfig();
clientConfig.readTimeout(0);
clientConfig.connectTimeout(0);
client = new RestClient(clientConfig);
String target = uri.substring(uri.indexOf("://") + 3);
int slash = target.indexOf('/');
target = target.substring(0, slash != -1 ? slash : target.length());
basePath = "http://" + target + "/" + DEFAULT_CONTEXT_PATH;
if (!isValidURI(basePath))
{
throw new IllegalArgumentException("The provided parameter is not a valid URL.");
}
return this;
}
/**
* Initializes an existent instance for the given host and port.
*
* @param host The target host.
* @param port The target port.
*/
public VSMClient initialize(final String host, final int port)
{
ClientConfig clientConfig = new ClientConfig();
clientConfig.readTimeout(0);
clientConfig.connectTimeout(0);
client = new RestClient(clientConfig);
basePath = "http://" + host + ":" + port + "/" + DEFAULT_CONTEXT_PATH;
if (!isValidURI(basePath))
{
throw new IllegalArgumentException("The provided parameters do not conform a valid URL.");
}
return this;
}
/**
* Get the list of monitored physical machines.
*
* @return The list of monitored physical machines.
* @throws VSMClientException If the list of monitored machines can not be retrieved.
*/
public PhysicalMachinesDto getMonitoredMachines() throws VSMClientException
{
Resource resource = client.resource(basePath + "/physicalmachines");
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).get();
checkResponseErrors(response);
return response.getEntity(PhysicalMachinesDto.class);
}
/**
* Gets the details of a monitored machine given its address.
*
* @param physicalMachineAddress The physical machine address.
* @return The monitored physical machine.
* @throws VSMClientException If there is no monitored machine with the given address.
*/
public PhysicalMachineDto getMonitoredMachine(final String physicalMachineAddress)
throws VSMClientException
{
Resource resource =
client.resource(basePath + "/physicalmachines?address="
+ encodeUTF8(physicalMachineAddress));
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).get();
checkResponseErrors(response);
return response.getEntity(PhysicalMachinesDto.class).getCollection().get(0);
}
/**
* Start monitoring the given physical machine.
*
* @param physicalMachineAddress The physical machine to monitor.
* @param type The hypervisor type of the physical machine.
* @param username The username used to connect to the hypervisor.
* @param password The password used to connect to the hypervisor.
* @return The just added physical machine.
* @throws VSMClientException If the monitor operation cannot be completed.
*/
public PhysicalMachineDto monitor(final String physicalMachineAddress, final String type,
final String username, final String password) throws VSMClientException
{
PhysicalMachineDto pm = new PhysicalMachineDto();
pm.setAddress(physicalMachineAddress);
pm.setType(type);
Resource resource = client.resource(basePath + "/physicalmachines");
String authHeader = "Basic " + toBasicAuth(username, password);
ClientResponse response =
resource.header("Authorization", authHeader).accept(MediaType.APPLICATION_XML_TYPE)
.contentType(MediaType.APPLICATION_XML_TYPE).post(pm);
checkResponseErrors(response);
return response.getEntity(PhysicalMachineDto.class);
}
/**
* Stop monitoring the given physical machine.
*
* @param physicalMachineAddress The physical machine to monitor.
* @throws VSMClientException If the shutdown operation cannot be completed.
*/
public void shutdown(final String physicalMachineAddress) throws VSMClientException
{
PhysicalMachineDto pm = getMonitoredMachine(physicalMachineAddress);
Resource resource = client.resource(basePath + "/physicalmachines/" + pm.getId());
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).delete();
checkResponseErrors(response);
}
/**
* Publish the current state of the given virtual machine.
*
* @param physicalMachineAddress The physical machine where the virtual machine is deployed.
* @param virtualMachineName The name of the virtual machine.
* @throws VSMClientException If the state of the virtual machine cannot be retrieved.
*/
public void publishState(final String physicalMachineAddress, final String virtualMachineName)
throws VSMClientException
{
PhysicalMachineDto pm = getMonitoredMachine(physicalMachineAddress);
String path =
basePath + "/physicalmachines/" + pm.getId() + "/virtualmachine/" + virtualMachineName;
Resource resource = client.resource(path);
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).get();
checkResponseErrors(response);
}
/**
* Invalidate the last known state of the given virtual machine.
*
* @param physicalMachineAddress The physical machine where the virtual machine is deployed.
* @param virtualMachineName The name of the virtual machine.
* @throws VSMClientException If the last known state of the virtual machine cannot be
* invalidated.
*/
public void invalidateLastKnownState(final String physicalMachineAddress,
final String virtualMachineName) throws VSMClientException
{
PhysicalMachineDto pm = getMonitoredMachine(physicalMachineAddress);
String path =
basePath + "/physicalmachines/" + pm.getId() + "/virtualmachine/" + virtualMachineName;
Resource resource = client.resource(path);
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).delete();
checkResponseErrors(response);
}
/**
* Get the list of current subscriptions.
*
* @return The list of current subscriptions.
* @throws VSMClientException If the list of current subscriptions cannot be retrieved.
*/
public VirtualMachinesDto getSubscriptions() throws VSMClientException
{
Resource resource = client.resource(basePath + "/subscriptions");
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).get();
checkResponseErrors(response);
return response.getEntity(VirtualMachinesDto.class);
}
/**
* Get the subscription for the given virtual machine.
*
* @param virtualMachineName The name of the virtual machine.
* @return The subscription for the virtual machine.
* @throws VSMClientException If there is no subscription for the given virtual machine.
* @throws UnsupportedEncodingException
*/
public VirtualMachineDto getSubscription(final String virtualMachineName)
throws VSMClientException
{
Resource resource =
client.resource(basePath + "/subscriptions?virtualmachine="
+ encodeUTF8(virtualMachineName));
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).get();
checkResponseErrors(response);
return response.getEntity(VirtualMachinesDto.class).getCollection().get(0);
}
/**
* Returns true if the subscription exists.
*
* @param virtualMachineName The name of the virtual machine.
* @return True if the subscription exists. Otherwise false.
*/
public boolean isSubscribed(final String virtualMachineName)
{
try
{
getSubscription(virtualMachineName);
return true;
}
catch (Exception e)
{
return false;
}
}
/**
* Returns true if the PhysicalMachine is already monitored.
*
* @param physicalMachineAddress The address of the physical machine
* @return True if the machine is already monitored. Otherwise false;
*/
public boolean isMonitored(final String physicalMachineAddress)
{
try
{
getMonitoredMachine(physicalMachineAddress);
return true;
}
catch (VSMClientException e)
{
return false;
}
}
/**
* Subscribes to changes in the given virtual machine.
*
* @param physicalMachineAddress The physical machine where the virtual machine is deployed.
* @param type The type of the hypervisor.
* @param virtualMachineName The name of the virtual machine.
* @return The subscription details to the virtual machine changes.
* @throws VSMClientException If the subscription operation cannot be completed.
*/
public VirtualMachineDto subscribe(final String physicalMachineAddress, final String type,
final String virtualMachineName) throws VSMClientException
{
PhysicalMachineDto pm = new PhysicalMachineDto();
pm.setAddress(physicalMachineAddress);
pm.setType(type);
VirtualMachineDto vm = new VirtualMachineDto();
vm.setName(virtualMachineName);
vm.setPhysicalMachine(pm);
Resource resource = client.resource(basePath + "/subscriptions");
ClientResponse response =
resource.accept(MediaType.APPLICATION_XML_TYPE)
.contentType(MediaType.APPLICATION_XML_TYPE).post(vm);
checkResponseErrors(response);
return response.getEntity(VirtualMachineDto.class);
}
/**
* Unsubscribes from changes to the given virtual machine.
*
* @param virtualMachineName The name of the virtual machine.
* @throws VSMClientException if the unsubscribe operation cannot be perforned.
*/
public void unsubscribe(final String virtualMachineName) throws VSMClientException
{
VirtualMachineDto vm = getSubscription(virtualMachineName);
Resource resource = client.resource(basePath + "/subscriptions/" + vm.getId());
ClientResponse response = resource.accept(MediaType.APPLICATION_XML_TYPE).delete();
checkResponseErrors(response);
}
/**
* Encode the given user name and password in a valid Basic Authentication format.
*
* @param username The user name to encode.
* @param password The password to encode.
* @return The Basic Authentication encoded credentials.
*/
private String toBasicAuth(final String username, final String password)
{
String token = username + ":" + password;
return new String(Base64.encodeBase64(token.getBytes()));
}
/**
* Check if the response has errors and throw the right exception.
*
* @param response The response to check.
* @throws VSMClientException A client exception if the response contains errors.
*/
private void checkResponseErrors(final ClientResponse response) throws VSMClientException
{
// All the 200 are accepted responses
if (response.getStatusCode() / 200 != 1)
{
Status status = Status.fromStatusCode(response.getStatusCode());
ErrorDto error = response.getEntity(ErrorDto.class);
if (error != null)
{
throw new VSMClientException(status, error.getMessage());
}
throw new VSMClientException(status,
"The Virtual System monitor is not properly configured");
}
}
/**
* Checks if the given URI is a valid URI.
*
* @param uri The URI to check.
* @return Boolean indicating if the given URI is a valid URI.
*/
private static boolean isValidURI(final String uri)
{
try
{
new URL(uri);
return true;
}
catch (MalformedURLException e)
{
return false;
}
}
private String encodeUTF8(final String value) throws VSMClientException
{
try
{
return URLEncoder.encode(value, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new VSMClientException(Status.INTERNAL_SERVER_ERROR, "Can not encode '" + value
+ "'. UTF-8 is an unsupported encoding.");
}
}
}