/* * Copyright (c) 2010 Ecole des Mines de Nantes. * * This file is part of Entropy. * * Entropy 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, either version 3 of the License, or * (at your option) any later version. * * Entropy 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 for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Entropy. If not, see <http://www.gnu.org/licenses/>. */ package entropy.execution.driver; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.xmlrpc.XmlRpcException; import org.apache.xmlrpc.client.XmlRpcClient; import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; /** * A Client to execute actions on the virtual machines hosted * on a Xen Hypervisor. This client uses the xen-api 1.0.6 * * In order to execute methods, you have to log in first using an username * and a password. If the authentification succeed, a session is opened and you * can execute methods. Then, you have to close your session using the method logout. * * @author Fabien Hermenier */ public class XenRpcClient { /** * The xmlrpc client. */ private XmlRpcClient client; /** * The current session Identifier. */ private String sessionID; /** * The default relocation port. */ public static final int DEFAULT_RELOCATION_PORT = 8002; /** * The default xen-api port. */ public static final int DEFAULT_XEN_API_PORT = 9363; /** * The default username to log in. */ public static final String DEFAULT_USERNAME = ""; /** * The default password of the user to log in. */ public static final String DEFAULT_PASSWORD = ""; /** * The remote port. */ private int port; /** * The remote hostname. */ private String hostname; /** * Make a new client and connect it to the port {@value #DEFAULT_XEN_API_PORT} of a remote * host. * @param host the xend hostname. * @throws MalformedURLException if an error occurred */ public XenRpcClient(String host) throws MalformedURLException { this(host, DEFAULT_XEN_API_PORT); } /** * Make a new client, connected to the specified port of a remote host. * @param host the xend hostname. * @param p the port of the xen-api * @throws MalformedURLException if an error occurred */ public XenRpcClient(String host, int p) throws MalformedURLException { this.hostname = host; this.port = p; URL url = new URL("http://" + hostname + ":" + port); XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); config.setServerURL(url); config.setEnabledForExtensions(true); this.client = new XmlRpcClient(); this.client.setConfig(config); } /** * login onto the client. * @param username the username * @param password the password * @return true if the operation succeed * @throws XenClientException if an error related to the operations occurred. * @throws XmlRpcException if an error occurred. */ public boolean login(String username, String password) throws XenClientException, XmlRpcException { this.sessionID = (String) this.execute("session.login_with_password", new Object [] {username, password}); return this.sessionID != null; } /** * Logout on the client. * @return true if the operation succeed * @throws XenClientException if an error related to the operations occurred. * @throws XmlRpcException if an error occurred */ public boolean logout() throws XenClientException, XmlRpcException { return this.execute("session.logout", new Object[] {this.sessionID}) != null; } /** * Migrate a virtual machine on a destination node. * The remote relocation port must be {@value #DEFAULT_RELOCATION_PORT} * @param name the name of the virtual machine * @param dest the hostname of the destination node * @param live set to true if migration is performed in live * @return true if the operation succeed * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public boolean migrate(String name, String dest, boolean live) throws XenClientException, XmlRpcException { return this.migrate(name, dest, live, DEFAULT_RELOCATION_PORT); } /** * Migrate a virtual machine on a destination node. * The remote relocation port must be {@value #DEFAULT_RELOCATION_PORT} * @param name the name of the virtual machine * @param dest the hostname of the destination node * @param live set to true if migration is performed in live * @param dstPort the remote relocation port * @return true if the operation succeed * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public boolean migrate(String name, String dest, boolean live, int dstPort) throws XenClientException, XmlRpcException { Map <String, Object>m = new HashMap<String, Object>(); m.put("port", dstPort); return this.execute("VM.migrate", new Object[]{this.sessionID, this.getUUID(name), dest, live, m}) != null; } /** * Save the virtual machine in a state file and free associated * CPU and memory resources. * @param name the name of the virtual machine * @param stateFile the file to store the state of the virtual machine * @param checkpoint run the VM after creating the checkpoint ? * @return true if the operation succeed * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public boolean save(String name, String stateFile, boolean checkpoint) throws XenClientException, XmlRpcException { String uuid = this.getUUID(name); if (uuid != null) { return this.execute("VM.save", new Object []{this.sessionID, uuid, stateFile, checkpoint}) != null; } return false; } /** * Execute a command and return the result if the method is executed. * @param method the method call * @param params the parameters of the method call * @return the return value * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ private Object execute(String method, Object [] params) throws XmlRpcException, XenClientException { HashMap <String, Object> result = (HashMap <String, Object>) this.client.execute(method, params); if (result.get("Status").equals("Success")) { return result.get("Value"); } else { Object [] toks = (Object[]) result.get("ErrorDescription"); if (toks[0].equals("PERMISSION_DENIED")) { throw new XenClientException(method, toks[0].toString(), "You do not have the permission to perform the operation"); } else if (toks[0].equals("OPERATION_NOT_ALLOWED")) { throw new XenClientException(method, toks[0].toString(), "You attempted an operation that was not allowed"); } else if (toks[0].equals("INTERNAL_ERROR") || toks[0].equals("MESSAGE_METHOD_UNKNOWN") || toks[0].equals("VM_HVM_REQUIRED")) { throw new XenClientException(method, toks[0].toString(), toks[1].toString()); } else if (toks[0].equals("INVALID_HANDLE")) { throw new XenClientException(method, toks[0].toString(), "Invalid handle: class=" + toks[1].toString() + " but value given=" + toks[2].toString()); } else if (toks[0].equals("MESSAGE_PARAMETER_COUNT_MISMATCH")) { throw new XenClientException(method, toks[0].toString(), "Incorrect number of parameters, expected " + toks[1].toString() + " but given " + toks[2].toString()); } else if (toks[0].equals("SESSION_AUTHENTIFICATION_FAILED")) { throw new XenClientException(method, toks[0].toString(), "The given session ID '" + params[0] + "' is incorrect"); } else if (toks[0].equals("SESSION_INVALID")) { throw new XenClientException(method, toks[0].toString(), "The given session ID '" + params[0] + "' is invalid (server restart or ID timed out)"); } else if (toks[0].equals("VALUE_NOT_SUPPORTED")) { throw new XenClientException(method, toks[0].toString(), "The value '" + toks[2].toString() + " of the field '" + toks[1].toString() + " is not supported : " + toks[3].toString()); } else if (toks[0].equals("VM_BAD_POWER_STATE")) { throw new XenClientException(method, toks[0].toString(), "The virtual machine '" + toks[1].toString() + "' is not in the good state to perform the operation. Expected '" + toks[2] + "' but was '" + toks[3] + "'"); } else if (toks[0].equals("SECURITY_ERROR")) { throw new XenClientException(method, toks[0].toString(), "Security error'" + toks[1].toString() + " - " + toks[2].toString()); } else { throw new XenClientException(method, toks[0].toString(), ""); } } } /** * Restore a virtual machine, previously saved into a file. * @param stateFile the file that contain the state of the virtual machine * @param run run the VM after the restart ? * @return true if the operation succeed * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public boolean restore(String stateFile, boolean run) throws XenClientException, XmlRpcException { return this.execute("VM.restore", new Object []{this.sessionID, stateFile, run}) != null; } /** * Get the UUID associated to the name of the virtual machine. * The name must be unique * @param name the name of the virtual machine * @return the UUID associated to the name * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public String getUUID(String name) throws XenClientException, XmlRpcException { Object [] res = (Object []) this.execute("VM.get_by_name_label", new Object[]{this.sessionID, name}); if (res.length > 0) { return res[0].toString(); } return null; } /** * Get the name of a virtual machine from its UUID. * @param uuid the UUID of the virtual machine * @return the name * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public String getNameFromUUID(String uuid) throws XenClientException, XmlRpcException { return (String) this.execute("VM.get_name_label", new Object[]{this.sessionID, uuid}); } /** * Shutdown the virtual machine. * @param name the name of the virtual machine * @return true if the operation succeed * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public boolean shutdown(String name) throws XenClientException, XmlRpcException { return this.execute("VM.clean_shutdown", new Object[] {this.sessionID, this.getUUID(name)}) != null; } /** * Hard shutdown of a virtual machine. * @param name the name of the virtual machine * @throws XmlRpcException if an error occurred * @return true if the operation succeed * @throws XenClientException if an error related to the operations occurred. */ public boolean destroy(String name) throws XenClientException, XmlRpcException { return this.execute("VML.hard_shutdown", new Object[] {this.sessionID, this.getUUID(name)}) != null; } /** * List the VMs. * @return a list of VMs, may be empty * @throws XmlRpcException if an error occurred * @throws XenClientException if an error related to the operations occurred. */ public List<String> listVMs() throws XenClientException, XmlRpcException { LinkedList<String> vms = new LinkedList<String>(); Object [] list = (Object []) this.execute("VM.get_all", new Object[] {this.sessionID}); for (Object o : list) { vms.add(this.getNameFromUUID(o.toString())); } return vms; } /** * Get the port of the xen api server. * @return the port */ public int getRemotePort() { return this.port; } /** * Get the name of the remote host. * @return an hostname */ public String getRemoteHostname() { return this.hostname; } }