| /* Copyright (c) Citrix Systems, Inc. | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * | |
| * 1) Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * | |
| * 2) Redistributions in binary form must reproduce the above | |
| * copyright notice, this list of conditions and the following | |
| * disclaimer in the documentation and/or other materials | |
| * provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
| * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |
| * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |
| * OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| package com.xensource.xenapi; | |
| import java.net.URL; | |
| import java.util.Map; | |
| import java.util.TimeZone; | |
| import org.apache.xmlrpc.XmlRpcException; | |
| import org.apache.xmlrpc.client.XmlRpcClient; | |
| import org.apache.xmlrpc.client.XmlRpcClientConfig; | |
| import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; | |
| import org.apache.xmlrpc.client.XmlRpcHttpClientConfig; | |
| import org.apache.xmlrpc.client.XmlRpcSun15HttpTransportFactory; | |
| import com.xensource.xenapi.Types.BadServerResponse; | |
| import com.xensource.xenapi.Types.SessionAuthenticationFailed; | |
| import com.xensource.xenapi.Types.XenAPIException; | |
| /** | |
| * Represents a connection to a XenServer. Creating a new instance of this class initialises a new XmlRpcClient that is | |
| * then used by all method calls: each method call in xenapi takes a Connection as a parameter, composes an XMLRPC | |
| * method call, and dispatches it on the Connection's client via the dispatch method. | |
| */ | |
| public class Connection | |
| { | |
| /** | |
| * The version of the bindings that this class belongs to. | |
| */ | |
| public static final String BINDINGS_VERSION = "6.1.0-1"; | |
| /** | |
| * true if the connection is to the Rio edition of XenServer. Certain function calls are not allowed. | |
| * | |
| * @deprecated Use getAPIVersion() instead. | |
| */ | |
| @Deprecated | |
| public Boolean rioConnection = false; | |
| private APIVersion apiVersion; | |
| protected int _wait = 600; | |
| /** | |
| * Updated when Session.login_with_password() is called. | |
| */ | |
| public APIVersion getAPIVersion() | |
| { | |
| return apiVersion; | |
| } | |
| /** | |
| * The opaque reference to the session used by this connection | |
| */ | |
| private String sessionReference; | |
| /** | |
| * As seen by the xmlrpc library. From our point of view it's a server. | |
| */ | |
| private final XmlRpcClient client; | |
| private final boolean deprecatedConstructorUsed; | |
| /** | |
| * Creates a connection to a particular server using a given username and password. This object can then be passed | |
| * in to any other API calls. | |
| * | |
| * This constructor calls Session.loginWithPassword, passing itself as the first parameter. | |
| * | |
| * When this constructor is used, a call to dispose() (also called in the Connection's finalizer) will attempt a | |
| * Session.logout on this connection. | |
| * | |
| * @deprecated Use a constructor that takes a URL as the first parameter instead. | |
| */ | |
| @Deprecated | |
| public Connection(String client, String username, String password) throws java.net.MalformedURLException, | |
| XmlRpcException, BadServerResponse, SessionAuthenticationFailed, XenAPIException | |
| { | |
| deprecatedConstructorUsed = true; | |
| // To login normally we call login_with_password(username, password, "1.X"). On rio this call fails and we | |
| // should use login_with_password(username,password) instead, and note that we are talking to a rio host so that we | |
| // can refuse to make certain miami-specific calls | |
| final String ApiVersion = APIVersion.latest().toString(); | |
| this.client = getClientFromURL(new URL(client)); | |
| try | |
| { | |
| //first try to login the modern way | |
| this.sessionReference = loginWithPassword(this.client, username, password, ApiVersion); | |
| } catch (BadServerResponse e) | |
| { | |
| //oops, something went wrong | |
| String[] errDesc = e.errorDescription; | |
| //was the problem that the host was running rio? If so it will have complained that it got three parameters | |
| //instead of two. Let us carefully verify the details of this complaint | |
| if (0 == errDesc[0].compareTo("MESSAGE_PARAMETER_COUNT_MISMATCH") | |
| && 0 == errDesc[1].compareTo("session.login_with_password") | |
| && 0 == errDesc[2].compareTo("2") | |
| && 0 == errDesc[3].compareTo("3")) | |
| { | |
| //and if so, we can have another go, using the older login method, and see how that goes. | |
| this.sessionReference = loginWithPassword(this.client, username, password); | |
| //success!. Note that we are talking to an old host on this connection | |
| this.rioConnection = true; | |
| } else | |
| { | |
| //Hmm... Can't solve this here. Let upstairs know about the problem. | |
| throw e; | |
| } | |
| } | |
| try | |
| { | |
| setAPIVersion(new Session(sessionReference)); | |
| } | |
| catch (XenAPIException exn) | |
| { | |
| dispose(); | |
| throw exn; | |
| } | |
| catch (XmlRpcException exn) | |
| { | |
| dispose(); | |
| throw exn; | |
| } | |
| } | |
| /** | |
| * Creates a connection to a particular server using a given username and password. This object can then be passed | |
| * in to any other API calls. | |
| * | |
| * Note this constructor does NOT call Session.loginWithPassword; the programmer is responsible for calling it, | |
| * passing the Connection as a parameter. No attempt to connect to the server is made until login is called. | |
| * | |
| * When this constructor is used, a call to dispose() will do nothing. The programmer is responsible for manually | |
| * logging out the Session. | |
| */ | |
| public Connection(URL url, int wait) | |
| { | |
| deprecatedConstructorUsed = false; | |
| _wait = wait; | |
| this.client = getClientFromURL(url); | |
| } | |
| /** | |
| * Creates a connection to a particular server using a given username and password. This object can then be passed | |
| * in to any other API calls. | |
| * | |
| * The additional sessionReference parameter must be a reference to a logged-in Session. Any method calls on this | |
| * Connection will use it. This constructor does not call Session.loginWithPassword, and dispose() on the resulting | |
| * Connection object does not call Session.logout. The programmer is responsible for ensuring the Session is logged | |
| * in and out correctly. | |
| */ | |
| public Connection(URL url, String sessionReference) | |
| { | |
| deprecatedConstructorUsed = false; | |
| this.client = getClientFromURL(url); | |
| this.sessionReference = sessionReference; | |
| } | |
| protected void finalize() throws Throwable | |
| { | |
| dispose(); | |
| super.finalize(); | |
| } | |
| /** | |
| * Nothrow guarantee. | |
| */ | |
| public void dispose() | |
| { | |
| if (!deprecatedConstructorUsed) | |
| { | |
| // We only need to do the Session.logout if they used the old deprecated constructor. | |
| return; | |
| } | |
| try | |
| { | |
| if (sessionReference != null) | |
| { | |
| String method_call = "session.logout"; | |
| Object[] method_params = { Marshalling.toXMLRPC(this.sessionReference) }; | |
| client.execute(method_call, method_params); | |
| sessionReference = null; | |
| } | |
| } | |
| catch (XmlRpcException exn) | |
| { | |
| } | |
| } | |
| /** | |
| * @deprecated The programmer is now responsible for calling login/logout themselves. | |
| */ | |
| @Deprecated | |
| private static String loginWithPassword(XmlRpcClient client, String username, String password) | |
| throws BadServerResponse, XmlRpcException, SessionAuthenticationFailed | |
| { | |
| String method_call = "session.login_with_password"; | |
| Object[] method_params = { Marshalling.toXMLRPC(username), Marshalling.toXMLRPC(password) }; | |
| Map response = (Map) client.execute(method_call, method_params); | |
| if (response.get("Status").equals("Success")) | |
| { | |
| return (String) response.get("Value"); | |
| } else if (response.get("Status").equals("Failure")) | |
| { | |
| Object[] error = (Object[]) response.get("ErrorDescription"); | |
| if (error[0].equals("SESSION_AUTHENTICATION_FAILED")) | |
| { | |
| throw new SessionAuthenticationFailed(); | |
| } | |
| } | |
| throw new BadServerResponse(response); | |
| } | |
| /** | |
| * @deprecated The programmer is now responsible for calling login/logout themselves. | |
| */ | |
| @Deprecated | |
| private static String loginWithPassword(XmlRpcClient client, String username, String password, String ApiVersion) | |
| throws BadServerResponse, XmlRpcException, SessionAuthenticationFailed | |
| { | |
| String method_call = "session.login_with_password"; | |
| Object[] method_params = { Marshalling.toXMLRPC(username), Marshalling.toXMLRPC(password), | |
| Marshalling.toXMLRPC(ApiVersion) }; | |
| Map response = (Map) client.execute(method_call, method_params); | |
| if (response.get("Status").equals("Success")) | |
| { | |
| return (String) response.get("Value"); | |
| } else if (response.get("Status").equals("Failure")) | |
| { | |
| Object[] error = (Object[]) response.get("ErrorDescription"); | |
| if (error[0].equals("SESSION_AUTHENTICATION_FAILED")) | |
| { | |
| throw new SessionAuthenticationFailed(); | |
| } | |
| } | |
| throw new BadServerResponse(response); | |
| } | |
| private XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); | |
| public XmlRpcClientConfigImpl getConfig() | |
| { | |
| return config; | |
| } | |
| private XmlRpcClient getClientFromURL(URL url) | |
| { | |
| config.setTimeZone(TimeZone.getTimeZone("UTC")); | |
| config.setServerURL(url); | |
| config.setReplyTimeout(_wait * 1000); | |
| config.setConnectionTimeout(5000); | |
| XmlRpcClient client = new XmlRpcClient(); | |
| client.setConfig(config); | |
| return client; | |
| } | |
| /* | |
| * Because the binding calls are constructing their own parameter lists, they need to be able to get to | |
| * the session reference directly. This is all rather ugly and needs redone | |
| * Changed to public to allow easier integration with HTTP-level streaming interface, | |
| * see CA-15447 | |
| */ | |
| public String getSessionReference() | |
| { | |
| return this.sessionReference; | |
| } | |
| /** | |
| * The (auto-generated parts of) the bindings dispatch XMLRPC calls on this Connection's client through this method. | |
| */ | |
| protected Map dispatch(String method_call, Object[] method_params) throws XmlRpcException, XenAPIException | |
| { | |
| Map response = (Map) client.execute(method_call, method_params); | |
| if (!deprecatedConstructorUsed) | |
| { | |
| // We are using the new-style constructor which doesn't perform login. | |
| // Set this Connection's Session reference from the value returned on the wire. | |
| if (method_call.equals("session.login_with_password") && | |
| response.get("Status").equals("Success")) | |
| { | |
| // Store the Session reference and ask the server what the | |
| // API version it's using is. | |
| Session session = Types.toSession(response.get("Value")); | |
| sessionReference = session.ref; | |
| setAPIVersion(session); | |
| } | |
| else if (method_call.equals("session.slave_local_login_with_password") && | |
| response.get("Status").equals("Success")) | |
| { | |
| // Store the Session reference and assume the latest API version. | |
| sessionReference = Types.toSession(response.get("Value")).ref; | |
| apiVersion = APIVersion.latest(); | |
| } | |
| else if (method_call.equals("session.logout")) | |
| { | |
| // Work around a bug in XenServer 5.0 and below. | |
| // session.login_with_password should have rejected us with | |
| // HOST_IS_SLAVE, but instead we don't find out until later. | |
| // We don't want to leak the session, so we need to log out | |
| // this session from the master instead. | |
| if (response.get("Status").equals("Failure")) | |
| { | |
| Object[] error = (Object[]) response.get("ErrorDescription"); | |
| if (error.length == 2 && error[0].equals("HOST_IS_SLAVE")) | |
| { | |
| try | |
| { | |
| URL client_url = | |
| ((XmlRpcHttpClientConfig)client.getClientConfig()).getServerURL(); | |
| Connection tmp_conn = | |
| new Connection(new URL(client_url.getProtocol(), | |
| (String)error[1], | |
| client_url.getPort(), | |
| client_url.getFile()), _wait); | |
| tmp_conn.sessionReference = sessionReference; | |
| try | |
| { | |
| Session.logout(tmp_conn); | |
| } | |
| finally | |
| { | |
| tmp_conn.dispose(); | |
| } | |
| } | |
| catch (Exception exn2) | |
| { | |
| // Ignore -- we're going to throw HostIsSlave anyway. | |
| } | |
| } | |
| } | |
| // Clear the stored Session reference. | |
| this.sessionReference = null; | |
| } | |
| } | |
| return Types.checkResponse(response); | |
| } | |
| private void setAPIVersion(Session session) throws XenAPIException, XmlRpcException | |
| { | |
| try | |
| { | |
| long major = session.getThisHost(this).getAPIVersionMajor(this); | |
| long minor = session.getThisHost(this).getAPIVersionMinor(this); | |
| apiVersion = APIVersion.fromMajorMinor(major, minor); | |
| } | |
| catch (BadServerResponse exn) | |
| { | |
| apiVersion = APIVersion.API_1_1; | |
| } | |
| } | |
| } |