content: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you 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 com.cloud.hypervisor.xenserver.resource;
  
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.PropertiesUtil;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.xensource.xenapi.APIVersion;
  import com.xensource.xenapi.Connection;
  import com.xensource.xenapi.ConnectionNew;
  import com.xensource.xenapi.Host;
  import com.xensource.xenapi.Pool;
  import com.xensource.xenapi.Session;
  import com.xensource.xenapi.Types;
  import com.xensource.xenapi.Types.BadServerResponse;
  import com.xensource.xenapi.Types.XenAPIException;
  import org.apache.cloudstack.utils.security.SSLUtils;
  import org.apache.cloudstack.utils.security.SecureSSLSocketFactory;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  import org.apache.xmlrpc.XmlRpcException;
  import org.apache.xmlrpc.client.XmlRpcClientException;
  
  import javax.net.ssl.HostnameVerifier;
  import javax.net.ssl.HttpsURLConnection;
  import javax.net.ssl.SSLSession;
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.net.URL;
  import java.security.KeyManagementException;
  import java.security.NoSuchAlgorithmException;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Properties;
  import java.util.Queue;
  
  public class XenServerConnectionPool {
      protected static Logger LOGGER = LogManager.getLogger(XenServerConnectionPool.class);
      protected HashMap<String /* poolUuid */, XenServerConnection> _conns = new HashMap<String, XenServerConnection>();
      protected int _retries;
      protected int _interval;
      protected int _connWait = 5;
      protected static long s_sleepOnError = 10 * 1000; // in ms
      static {
          File file = PropertiesUtil.findConfigFile("environment.properties");
          if (file == null) {
              LOGGER.debug("Unable to find environment.properties");
          } else {
              try {
                  final Properties props = PropertiesUtil.loadFromFile(file);
                  String search = props.getProperty("sleep.interval.on.error");
                  if (search != null) {
                      s_sleepOnError = NumbersUtil.parseInterval(search, 10) * 1000;
                  }
                  LOGGER.info("XenServer Connection Pool Configs: sleep.interval.on.error=" + s_sleepOnError);
              } catch (FileNotFoundException e) {
                  LOGGER.debug("File is not found", e);
              } catch (IOException e) {
                  LOGGER.debug("IO Exception while reading file", e);
              }
          }
          try {
              javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
              javax.net.ssl.TrustManager tm = new TrustAllManager();
              trustAllCerts[0] = tm;
              javax.net.ssl.SSLContext sc = SSLUtils.getSSLContext();
              sc.init(null, trustAllCerts, null);
              javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(new SecureSSLSocketFactory(sc));
              HostnameVerifier hv = new HostnameVerifier() {
                  @Override
                  public boolean verify(String hostName, SSLSession session) {
                      return true;
                  }
              };
              HttpsURLConnection.setDefaultHostnameVerifier(hv);
          } catch (NoSuchAlgorithmException e) {
              //ignore this
          } catch (KeyManagementException e) {
              LOGGER.debug("Init SSLContext failed ", e);
          }
      }
  
      protected XenServerConnectionPool() {
          _retries = 1;
          _interval = 3;
      }
  
      private void addConnect(String poolUuid, XenServerConnection conn) {
          if (poolUuid == null)
              return;
          if (LOGGER.isDebugEnabled()) {
              LOGGER.debug("Add master connection through " + conn.getIp() + " for pool(" + conn.getPoolUuid() + ")");
          }
          synchronized (_conns) {
              _conns.put(poolUuid, conn);
          }
      }
  
      private XenServerConnection getConnect(String poolUuid) {
          if (poolUuid == null)
              return null;
          synchronized (_conns) {
              return _conns.get(poolUuid);
          }
      }
  
      private void removeConnect(String poolUuid) {
          if (poolUuid == null) {
              return;
          }
          XenServerConnection conn = null;
          synchronized (_conns) {
              conn = _conns.remove(poolUuid);
          }
          if (conn != null) {
              if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug("Remove master connection through " + conn.getIp() + " for pool(" + conn.getPoolUuid() + ")");
              }
  
          }
      }
  
      static void forceSleep(long sec) {
          long firetime = System.currentTimeMillis() + (sec * 1000);
          long msec = sec * 1000;
          while (true) {
              if (msec < 100)
                  break;
              try {
                  Thread.sleep(msec);
                  return;
              } catch (InterruptedException e) {
                  msec = firetime - System.currentTimeMillis();
              }
          }
      }
  
      public Connection getConnect(String ip, String username, Queue<String> password) {
          Connection conn = new ConnectionNew(getURL(ip), 10, _connWait);
          try {
              loginWithPassword(conn, username, password, APIVersion.latest().toString());
          }  catch (Types.HostIsSlave e) {
              String maddress = e.masterIPAddress;
              conn = new ConnectionNew(getURL(maddress), 10, _connWait);
              try {
                  loginWithPassword(conn, username, password, APIVersion.latest().toString());
              }  catch (Exception e1) {
                  String msg = "Unable to create master connection to host(" + maddress +") , due to " + e1.toString();
                  LOGGER.debug(msg);
                  throw new CloudRuntimeException(msg, e1);
              }
          } catch (Exception e) {
              String msg = "Unable to create master connection to host(" + ip +") , due to " + e.toString();
              LOGGER.debug(msg);
              throw new CloudRuntimeException(msg, e);
          }
          return conn;
      }
  
      public URL getURL(String ip) {
          try {
              return new URL("https://" + ip);
          } catch (Exception e) {
              String msg = "Unable to convert IP " + ip + " to URL due to " + e.toString();
              if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug(msg);
              }
              throw new CloudRuntimeException(msg, e);
          }
      }
  
      public Connection connect(String hostUuid, String poolUuid, String ipAddress,
              String username, Queue<String> password, int wait) {
          XenServerConnection mConn = null;
          if (hostUuid == null || poolUuid == null || ipAddress == null || username == null || password == null) {
              String msg = "Connect some parameter are null hostUuid:" + hostUuid + " ,poolUuid:" + poolUuid
                      + " ,ipAddress:" + ipAddress;
              LOGGER.debug(msg);
              throw new CloudRuntimeException(msg);
          }
          synchronized (poolUuid.intern()) {
              mConn = getConnect(poolUuid);
              if (mConn != null){
                  try{
                      Host host = Host.getByUuid(mConn, hostUuid);
                      if (!host.getEnabled(mConn)) {
                          String msg = "Cannot connect this host " + ipAddress + " due to the host is not enabled";
                          LOGGER.debug(msg);
                          if (mConn.getIp().equalsIgnoreCase(ipAddress)) {
                              removeConnect(poolUuid);
                              mConn = null;
                          }
                          throw new CloudRuntimeException(msg);
                      }
                      return mConn;
                  } catch (CloudRuntimeException e) {
                          throw e;
                  } catch (Exception e) {
                      if (LOGGER.isDebugEnabled()) {
                          String ip = mConn != null ? mConn.getIp() : null;
                          LOGGER.debug("connect through IP(" + ip + ") for pool(" + poolUuid + ") is broken due to " + e.toString());
                      }
                      removeConnect(poolUuid);
                      mConn = null;
                  }
              }
  
              if ( mConn == null ) {
                  try {
                      Connection conn = new ConnectionNew(getURL(ipAddress), 5, _connWait);
                      Session sess = loginWithPassword(conn, username, password, APIVersion.latest().toString());
                      Host host = sess.getThisHost(conn);
                      Boolean hostenabled = host.getEnabled(conn);
                      if( sess != null ){
                          try{
                              Session.logout(conn);
                          } catch (Exception e) {
                              LOGGER.debug("Caught exception during logout", e);
                          }
                      }
                      if (!hostenabled) {
                          String msg = "Unable to create master connection, due to master Host " + ipAddress + " is not enabled";
                          LOGGER.debug(msg);
                          throw new CloudRuntimeException(msg);
                      }
                      mConn = new XenServerConnection(getURL(ipAddress), ipAddress, username, password, _retries, _interval, wait, _connWait);
                      loginWithPassword(mConn, username, password, APIVersion.latest().toString());
                  }  catch (Types.HostIsSlave e) {
                      String maddress = e.masterIPAddress;
                      mConn = new XenServerConnection(getURL(maddress), maddress, username, password, _retries, _interval, wait, _connWait);
                      try {
                          Session session = loginWithPassword(mConn, username, password, APIVersion.latest().toString());
                          Host host = session.getThisHost(mConn);
                          if (!host.getEnabled(mConn)) {
                              String msg = "Unable to create master connection, due to master Host " + maddress + " is not enabled";
                              LOGGER.debug(msg);
                              throw new CloudRuntimeException(msg);
                          }
                      }  catch (Exception e1) {
                          String msg = "Unable to create master connection to host(" + maddress +") , due to " + e1.toString();
                          LOGGER.debug(msg);
                          throw new CloudRuntimeException(msg, e1);
  
                      }
                  } catch (CloudRuntimeException e) {
                          throw e;
                  } catch (Exception e) {
                      String msg = "Unable to create master connection to host(" + ipAddress +") , due to " + e.toString();
                      LOGGER.debug(msg);
                      throw new CloudRuntimeException(msg, e);
                  }
                  addConnect(poolUuid, mConn);
              }
          }
          return mConn;
      }
  
  
  
      protected Session slaveLocalLoginWithPassword(Connection conn, String username, Queue<String> password) throws BadServerResponse, XenAPIException, XmlRpcException {
          Session s = null;
          boolean logged_in = false;
          Exception ex = null;
          while (!logged_in) {
              try {
                  s = Session.slaveLocalLoginWithPassword(conn, username, password.peek());
                  logged_in = true;
              } catch (BadServerResponse e) {
                  logged_in = false;
                  ex = e;
              } catch (XenAPIException e) {
                  logged_in = false;
                  ex = e;
              } catch (XmlRpcException e) {
                  logged_in = false;
                  ex = e;
              }
              if (logged_in && conn != null) {
                  break;
              } else {
                  if (password.size() > 1) {
                      password.remove();
                      continue;
                  } else {
                      // the last password did not work leave it and flag error
                      if (ex instanceof BadServerResponse) {
                          throw (BadServerResponse)ex;
                      } else if (ex instanceof XmlRpcException) {
                          throw (XmlRpcException)ex;
                      } else if (ex instanceof Types.SessionAuthenticationFailed) {
                          throw (Types.SessionAuthenticationFailed)ex;
                      } else if (ex instanceof XenAPIException) {
                          throw (XenAPIException)ex;
                      }
                      break;
                  }
              }
          }
          return s;
      }
  
      protected Session loginWithPassword(Connection conn, String username, Queue<String> password, String version) throws BadServerResponse, XenAPIException,
          XmlRpcException {
          Session s = null;
          boolean logged_in = false;
          Exception ex = null;
          while (!logged_in) {
              try {
                  s = Session.loginWithPassword(conn, username, password.peek(), APIVersion.latest().toString());
                  logged_in = true;
              } catch (BadServerResponse e) {
                  logged_in = false;
                  ex = e;
              } catch (XenAPIException e) {
                  logged_in = false;
                  ex = e;
              } catch (XmlRpcException e) {
                  logged_in = false;
                  ex = e;
              }
  
              if (logged_in && conn != null) {
                  break;
              } else {
                  if (password.size() > 1) {
                      password.remove();
                      continue;
                  } else {
                      // the last password did not work leave it and flag error
                      if (ex instanceof BadServerResponse) {
                          throw (BadServerResponse)ex;
                      } else if (ex instanceof XmlRpcException) {
                          throw (XmlRpcException)ex;
                      } else if (ex instanceof Types.SessionAuthenticationFailed) {
                          throw (Types.SessionAuthenticationFailed)ex;
                      } else if (ex instanceof XenAPIException) {
                          throw (XenAPIException)ex;
                      }
                  }
              }
          }
          return s;
      }
  
      protected void join(Connection conn, String masterIp, String username, Queue<String> password) throws BadServerResponse, XenAPIException, XmlRpcException,
          Types.JoiningHostCannotContainSharedSrs {
  
          boolean logged_in = false;
          Exception ex = null;
          while (!logged_in) {
              try {
                  Pool.join(conn, masterIp, username, password.peek());
                  logged_in = true;
              } catch (BadServerResponse e) {
                  logged_in = false;
                  ex = e;
              } catch (XenAPIException e) {
                  logged_in = false;
                  ex = e;
              } catch (XmlRpcException e) {
                  logged_in = false;
                  ex = e;
              }
              if (logged_in && conn != null) {
                  break;
              } else {
                  if (password.size() > 1) {
                      password.remove();
                      continue;
                  } else {
                      // the last password did not work leave it and flag error
                      if (ex instanceof BadServerResponse) {
                          throw (BadServerResponse)ex;
                      } else if (ex instanceof XmlRpcException) {
                          throw (XmlRpcException)ex;
                      } else if (ex instanceof Types.SessionAuthenticationFailed) {
                          throw (Types.SessionAuthenticationFailed)ex;
                      } else if (ex instanceof XenAPIException) {
                          throw (XenAPIException)ex;
                      }
                      break;
                  }
              }
          }
      }
  
      static public Pool.Record getPoolRecord(Connection conn) throws XmlRpcException, XenAPIException {
          Map<Pool, Pool.Record> pools = Pool.getAllRecords(conn);
          assert pools.size() == 1 : "Pool size is not one....hmmm....wth? " + pools.size();
  
          return pools.values().iterator().next();
      }
  
      private static final XenServerConnectionPool s_instance = new XenServerConnectionPool();
  
      public static XenServerConnectionPool getInstance() {
          return s_instance;
      }
  
      public class XenServerConnection extends ConnectionNew {
          long _interval;
          int _retries;
          String _ip;
          String _username;
          Queue<String> _password;
          String _poolUuid;
  
          public XenServerConnection(URL url, String ip, String username, Queue<String> password, int retries, int interval, int wait, int connwait) {
              super(url, wait, connwait);
              _ip = ip;
              _retries = retries;
              _username = username;
              _password = password;
              _interval = (long)interval * 1000;
  
          }
  
          public String getPoolUuid() {
              return _poolUuid;
          }
  
          public String getUsername() {
              return _username;
          }
  
          public Queue<String> getPassword() {
              return _password;
          }
  
          public String getIp() {
              return _ip;
          }
  
          @Override
          protected Map dispatch(String methodcall, Object[] methodparams)  throws XmlRpcException, XenAPIException {
              if (methodcall.equals("session.local_logout")
                      || methodcall.equals("session.slave_local_login_with_password")
                      || methodcall.equals("session.logout")
                      || methodcall.equals("session.login_with_password")) {
                  return super.dispatch(methodcall, methodparams);
              }
  
              try {
                  return super.dispatch(methodcall, methodparams);
              } catch (Types.SessionInvalid e) {
                  LOGGER.debug("Session is invalid for method: " + methodcall + " due to " + e.toString());
                  removeConnect(_poolUuid);
                  throw e;
              } catch (XmlRpcClientException e) {
                  LOGGER.debug("XmlRpcClientException for method: " + methodcall + " due to " + e.toString());
                  removeConnect(_poolUuid);
                  throw e;
              } catch (XmlRpcException e) {
                  LOGGER.debug("XmlRpcException for method: " + methodcall + " due to " + e.toString());
                  removeConnect(_poolUuid);
                  throw e;
              } catch (Types.HostIsSlave e) {
                   LOGGER.debug("HostIsSlave Exception for method: " + methodcall + " due to " + e.toString());
                   removeConnect(_poolUuid);
                   throw e;
              }
          }
      }
  
      public static class TrustAllManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
          @Override
          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
              return null;
          }
  
          public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
              return true;
          }
  
          public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
              return true;
          }
  
          @Override
          public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {
              return;
          }
  
          @Override
          public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {
              return;
          }
      }
  
  }
  