Package org.cloudfoundry.ide.eclipse.server.core.internal.client

Source Code of org.cloudfoundry.ide.eclipse.server.core.internal.client.TunnelBehaviour

/*******************************************************************************
* Copyright (c) 2012, 2014 Pivotal Software, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of 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.
*  Contributors:
*     Pivotal Software, Inc. - initial API and implementation
********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.core.internal.client;

import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.cloudfoundry.caldecott.TunnelException;
import org.cloudfoundry.caldecott.client.HttpTunnelFactory;
import org.cloudfoundry.caldecott.client.TunnelFactory;
import org.cloudfoundry.caldecott.client.TunnelHelper;
import org.cloudfoundry.caldecott.client.TunnelServer;
import org.cloudfoundry.client.lib.CloudFoundryClient;
import org.cloudfoundry.client.lib.CloudFoundryOperations;
import org.cloudfoundry.client.lib.HttpProxyConfiguration;
import org.cloudfoundry.client.lib.domain.CloudApplication;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudErrorUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryCallback;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryPlugin;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.core.internal.tunnel.CaldecottTunnelDescriptor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.wst.server.core.IModule;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
* Primary handler for all Caldecott operations, like starting and stopping a
* tunnel.
* <p/>
* If a Caldecott app is not yet deployed, this handler will automatically
* deploy and start it.
* <p/>
*
* When creating a tunnel for a service that hasn't been bound, this handler
* will automatically bind the service first to the Caldecott application before
* attempting to create a tunnel.
* <p/>
* NOTE: CloudFoundryOperation calls should ALWAYS be done through a
* org.cloudfoundry
* .ide.eclipse.internal.server.core.CloudFoundryServerBehaviour.Request object,
* as the Request wraps around all client calls. Only exception is if doing
* standalone calls to a CF server, like validating credentials or getting a
* list of organisations and spaces.
*
*/
public class TunnelBehaviour {

  public static final String LOCAL_HOST = "127.0.0.1"; //$NON-NLS-1$

  private final CloudFoundryServer cloudServer;

  public static final int BASE_PORT = 10100;

  public static final int MAX_PORT = 49150;

  public TunnelBehaviour(CloudFoundryServer cloudServer) {
    this.cloudServer = cloudServer;
  }

  protected boolean bindServiceToCaldecottApp(String serviceName, CloudFoundryOperations client, SubMonitor monitor)
      throws CoreException {

    CloudApplication caldecottApp = getCaldecottApp(client);
    List<String> updateCaldecottServices = new ArrayList<String>();
    List<String> existingServices = caldecottApp.getServices();
    if (existingServices != null) {
      // Must iterate to filter out possible null service names
      for (String existing : existingServices) {
        if (existing != null) {
          updateCaldecottServices.add(existing);
        }
      }
    }

    IModule caldecottModule = getCaldecottModule(monitor.newChild(1));

    if (!updateCaldecottServices.contains(serviceName)) {
      monitor.setTaskName("Binding service " + serviceName + " to tunnel application"); //$NON-NLS-1$ //$NON-NLS-2$

      updateCaldecottServices.add(serviceName);
      CloudFoundryServerBehaviour behaviour = cloudServer.getBehaviour();
      behaviour.stopModule(new IModule[] { caldecottModule }, monitor.newChild(1));
      behaviour.updateServices(TunnelHelper.getTunnelAppName(), updateCaldecottServices, monitor.newChild(1));

      setDeploymentServices(serviceName, monitor.newChild(1));

      return caldecottApp.getServices().contains(serviceName);
    }
    else {
      return true;
    }

  }

  public static boolean isCaldecottApp(String appName) {
    return TunnelHelper.getTunnelAppName().equals(appName);
  }

  protected void startCaldecottApp(IProgressMonitor progress, final CloudFoundryOperations client)
      throws CoreException {
    progress.setTaskName("Starting tunnel application"); //$NON-NLS-1$
    CloudApplication caldecottApp = getCaldecottApp(client);

    if (caldecottApp == null) {
      throw CloudErrorUtil.toCoreException("No Caldecott application found. Unable to create tunnel."); //$NON-NLS-1$
    }
    CloudFoundryApplicationModule appModule = cloudServer.getExistingCloudModule(caldecottApp.getName());
    if (appModule == null) {
      throw CloudErrorUtil
          .toCoreException("No local Caldecott application module found. Application may not have finished deploying. Unable to create tunnel."); //$NON-NLS-1$
    }

    cloudServer.getBehaviour().startModule(new IModule[] { appModule.getLocalModule() }, progress);

    // Wait til application has started
    new WaitApplicationToStartOp(cloudServer, appModule).run(progress);

  }

  protected String getTunnelUri(final CloudFoundryOperations client, IProgressMonitor progress) throws CoreException {
    int attempts = 10;
    long sleep = 3000;

    progress.setTaskName("Getting tunnel URL"); //$NON-NLS-1$
    String url = new AbstractWaitWithProgressJob<String>(attempts, sleep) {

      @Override
      protected String runInWait(IProgressMonitor monitor) throws CoreException {
        if (client instanceof CloudFoundryClient) {
          return TunnelHelper.getTunnelUri((CloudFoundryClient) client);
        }
        return null;
      }

      protected boolean shouldRetryOnError(Throwable t) {
        // Try several times in case 404 errors are thrown
        return true;
      }

    }.run(progress);

    return url;
  }

  protected String getTunnelAuthorisation(CloudFoundryOperations operations) {
    if (operations instanceof CloudFoundryClient) {
      return TunnelHelper.getTunnelAuth((CloudFoundryClient) operations);
    }
    return null;
  }

  public synchronized CaldecottTunnelDescriptor startCaldecottTunnel(final String serviceName,
      IProgressMonitor monitor, final boolean shouldShowTunnelInformation) throws CoreException {

    final List<CaldecottTunnelDescriptor> tunnel = new ArrayList<CaldecottTunnelDescriptor>(1);

    new LocalServerRequest<CaldecottTunnelDescriptor>("Opening Tunnel") { //$NON-NLS-1$

      @Override
      protected CaldecottTunnelDescriptor doRun(final CloudFoundryOperations client, SubMonitor progress)
          throws CoreException {
        int totalWorkTicks = 100;
        int worked = 10;

        progress = SubMonitor.convert(progress, totalWorkTicks);

        CloudApplication caldecottApp = getOrDeployCaldecottApp(getSubMonitor(worked, progress), client);

        if (caldecottApp == null) {
          return null;
        }

        bindServiceToCaldecottApp(serviceName, client, getSubMonitor(worked, progress));

        // The application must be started before creating a tunnel

        startCaldecottApp(getSubMonitor(worked, progress), client);

        CaldecottTunnelDescriptor oldDescriptor = CloudFoundryPlugin.getCaldecottTunnelCache().getDescriptor(
            cloudServer, serviceName);

        if (oldDescriptor != null) {
          try {
            progress.setTaskName("Stopping existing tunnel"); //$NON-NLS-1$
            stopAndDeleteCaldecottTunnel(serviceName, getSubMonitor(worked, progress));

          }
          catch (CoreException e) {
            CloudFoundryPlugin
                .logError("Failed to stop existing tunnel for service " + serviceName + ". Unable to create new tunnel."); //$NON-NLS-1$ //$NON-NLS-2$
            return null;
          }
        }

        String url = getTunnelUri(client, getSubMonitor(worked, progress));

        Map<String, String> info = getTunnelInfo(client, serviceName, getSubMonitor(worked, progress));
        if (info == null) {
          CloudFoundryPlugin.logError("Failed to obtain tunnel information for " + serviceName); //$NON-NLS-1$
          return null;
        }

        String host = info.get("hostname"); //$NON-NLS-1$
        int port = Integer.valueOf(info.get("port")); //$NON-NLS-1$
        String auth = getTunnelAuthorisation(client);
        String serviceUserName = info.get("username"); //$NON-NLS-1$
        String servicePassword = info.get("password"); //$NON-NLS-1$
        String dataBase = getServiceVendor(serviceName, getSubMonitor(worked, progress));

        String name = info.get("vhost"); //$NON-NLS-1$
        if (name == null) {
          name = info.get("db") != null ? info.get("db") : info.get("name"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }

        // Use proxy settings if they exist

        HttpProxyConfiguration proxyConfiguration = null;

        try {
          URL urlOb = new URL(url);
          proxyConfiguration = CloudFoundryClientFactory.getProxy(urlOb);
        }
        catch (MalformedURLException e) {
          // Unable to handle proxy URL. Attempt to connect anyway.
        }

        TunnelFactory tunnelFactory = new HttpTunnelFactory(url, host, port, auth, proxyConfiguration);

        List<TunnelServer> tunnelServers = new ArrayList<TunnelServer>(1);
        int localPort = getTunnelServer(tunnelFactory, tunnelServers);

        if (tunnelServers.isEmpty() || localPort == -1) {
          CloudFoundryPlugin.logError("Tunnel information obtained for " + serviceName + //$NON-NLS-1$
              ", but failed to create tunnel server for ports between: " + new Integer(BASE_PORT)
              + " and " + new Integer(MAX_PORT)); //$NON-NLS-1$ //$NON-NLS-2$
          return null;
        }

        TunnelServer tunnelServer = tunnelServers.get(0);

        progress.setTaskName("Starting tunnel server"); //$NON-NLS-1$
        tunnelServer.start();

        CaldecottTunnelDescriptor descriptor = new CaldecottTunnelDescriptor(serviceUserName, servicePassword,
            name, serviceName, dataBase, tunnelServer, localPort);

        CloudFoundryPlugin.getCaldecottTunnelCache().addDescriptor(cloudServer, descriptor);
        tunnel.add(descriptor);

        CloudFoundryCallback callBack = CloudFoundryPlugin.getCallback();
        List<CaldecottTunnelDescriptor> descriptors = new ArrayList<CaldecottTunnelDescriptor>();
        descriptors.add(descriptor);

        // Update any UI that needs to be notified that a tunnel was
        // created
        if (shouldShowTunnelInformation) {
          callBack.displayCaldecottTunnelConnections(cloudServer, descriptors);
        }

        return descriptor;
      }

      @Override
      protected CloudFoundryServer getCloudServer() throws CoreException {
        return cloudServer;
      }

    }.run(monitor);

    return tunnel.size() > 0 ? tunnel.get(0) : null;
  }

  /**
   * Updates the monitor for work done, and also checks if the monitor has
   * been cancelled.
   * @param workDoneInTicks
   * @param monitor
   * @throws OperationCanceledException if operation is cancelled.
   */
  protected SubMonitor getSubMonitor(int workDoneInTicks, SubMonitor monitor) {
    if (!monitor.isCanceled()) {
      return monitor.newChild(workDoneInTicks);
    }
    else {
      throw new OperationCanceledException();
    }
  }

  /**
   *
   * @param tunnelFactory
   * @param server non null, where created tunnel will be stored.
   * @return -1 if port failed to open
   * @throws CoreException
   */
  protected int getTunnelServer(TunnelFactory tunnelFactory, List<TunnelServer> tunnelServers) throws CoreException {

    RuntimeException se = null;

    int port = -1;
    TunnelServer tunnelServer = null;
    for (int i = BASE_PORT; i <= MAX_PORT; i++) {

      try {
        InetSocketAddress local = new InetSocketAddress(LOCAL_HOST, i);
        tunnelServer = new TunnelServer(local, tunnelFactory, getTunnelServerThreadExecutor());
        tunnelServers.add(tunnelServer);
        port = i;
        break;
      }
      catch (TunnelException e) {
        se = e;
      }
      catch (SecurityException e) {
        se = e;
      }

    }
    // Only rethrow security exception if all ports have failed
    if (tunnelServer == null && se != null) {
      throw new CoreException(CloudFoundryPlugin.getErrorStatus(se));
    }
    return port;

  }

  /**
   * Sets the new service in the existing deployment info for an existing
   * Caldecott ApplicationModule, if and only if the application module
   * already has a deployment info and does not yet contain the service.
   * Returns true if the latter iff condition is met, false any other case.
   * @param serviceName
   * @param monitor
   * @return
   */
  protected boolean setDeploymentServices(String serviceName, IProgressMonitor monitor) throws CoreException {

    boolean serviceChanges = false;

    CloudFoundryApplicationModule appModule = getCaldecottModule(monitor);

    if (appModule != null) {

      DeploymentInfoWorkingCopy deploymentInfo = appModule.resolveDeploymentInfoWorkingCopy(monitor);

      if (deploymentInfo != null) {
        List<CloudService> existingServices = deploymentInfo.getServices();
        List<CloudService> updatedServices = new ArrayList<CloudService>();
        if (existingServices != null) {
          updatedServices.addAll(existingServices);
        }

        CloudService existingService = null;
        for (CloudService service : updatedServices) {
          if (service.getName().equals(serviceName)) {
            existingService = service;
            break;
          }
        }

        if (existingService == null) {
          updatedServices.add(new LocalCloudService(serviceName));
          deploymentInfo.setServices(updatedServices);
          deploymentInfo.save();
          serviceChanges = true;
        }
      }
    }

    return serviceChanges;
  }

  protected TaskExecutor getTunnelServerThreadExecutor() {
    int defaultPoolSize = 20;
    ThreadPoolTaskExecutor te = new ThreadPoolTaskExecutor();
    te.setCorePoolSize(defaultPoolSize);
    te.setMaxPoolSize(defaultPoolSize * 2);
    te.setQueueCapacity(100);
    return te;
  }

  protected String getServiceVendor(String serviceName, IProgressMonitor monitor) throws CoreException {
    List<CloudService> services = cloudServer.getBehaviour().getServices(monitor);
    if (services != null) {
      for (CloudService service : services) {
        if (serviceName.equals(service.getName())) {
          return service.getLabel();
        }
      }
    }
    return null;
  }

  protected Map<String, String> getTunnelInfo(final CloudFoundryOperations client, final String serviceName,
      IProgressMonitor monitor) throws CoreException {
    monitor.setTaskName("Getting tunnel information"); //$NON-NLS-1$
    int attempts = 10;
    long sleepTime = 2000;

    Map<String, String> info = new AbstractWaitWithProgressJob<Map<String, String>>(attempts, sleepTime) {

      @Override
      protected Map<String, String> runInWait(IProgressMonitor monitor) {
        if (client instanceof CloudFoundryClient) {
          return TunnelHelper.getTunnelServiceInfo((CloudFoundryClient) client, serviceName);
        }
        return null;
      }

      @Override
      protected boolean shouldRetryOnError(Throwable t) {
        // Try several times in case 404 errors are thrown
        return true;
      }
    }.run(monitor);

    if (info == null) {
      CloudFoundryPlugin.logError("Timeout trying to obtain tunnel information for: " + serviceName //$NON-NLS-1$
          + ". Please wait a few seconds before trying again."); //$NON-NLS-1$
    }

    return info;
  }

  public synchronized CaldecottTunnelDescriptor stopAndDeleteCaldecottTunnel(String serviceName,
      IProgressMonitor monitor) throws CoreException {

    CaldecottTunnelDescriptor tunnelDescriptor = stopCaldecottTunnel(serviceName);
    if (tunnelDescriptor != null) {
      CloudFoundryPlugin.getCaldecottTunnelCache().removeDescriptor(cloudServer,
          tunnelDescriptor.getServiceName());
    }
    return tunnelDescriptor;

  }

  /**
   * Stops and deletes all Caldecott tunnels for the given server.
   * @param monitor
   * @throws CoreException
   */
  public synchronized void stopAndDeleteAllTunnels(IProgressMonitor monitor) throws CoreException {
    Collection<CaldecottTunnelDescriptor> descriptors = CloudFoundryPlugin.getCaldecottTunnelCache()
        .getDescriptors(cloudServer);
    if (descriptors != null) {
      for (CaldecottTunnelDescriptor desc : descriptors) {
        stopAndDeleteCaldecottTunnel(desc.getServiceName(), monitor);
      }
    }
  }

  public synchronized CaldecottTunnelDescriptor stopCaldecottTunnel(String serviceName) throws CoreException {

    CaldecottTunnelDescriptor tunnelDescriptor = CloudFoundryPlugin.getCaldecottTunnelCache().getDescriptor(
        cloudServer, serviceName);
    if (tunnelDescriptor != null) {
      tunnelDescriptor.getTunnelServer().stop();
    }
    return tunnelDescriptor;
  }

  public synchronized boolean hasCaldecottTunnels() {
    Collection<CaldecottTunnelDescriptor> descriptors = CloudFoundryPlugin.getCaldecottTunnelCache()
        .getDescriptors(cloudServer);
    return descriptors != null && descriptors.size() > 0;
  }

  /**
   * Returns an a tunnel descriptor if the service currently is connected via
   * a tunnel, or null if no open tunnel exists
   * @param serviceName
   * @return
   */
  public synchronized CaldecottTunnelDescriptor getCaldecottTunnel(String serviceName) {

    return CloudFoundryPlugin.getCaldecottTunnelCache().getDescriptor(cloudServer, serviceName);
  }

  public synchronized boolean hasCaldecottTunnel(String serviceName) {
    return getCaldecottTunnel(serviceName) != null;
  }

  protected synchronized CloudApplication getCaldecottApp(CloudFoundryOperations client) throws CoreException {

    CloudApplication caldecottApp = null;
    try {
      caldecottApp = client.getApplication(TunnelHelper.getTunnelAppName());
    }
    catch (Throwable e) {
      throw new CoreException(CloudFoundryPlugin.getErrorStatus(e));
    }
    return caldecottApp;
  }

  /**
   * Retrieves the actual Caldecott Cloud Application from the server. It does
   * not rely on webtools IModule. May be a long running operation and
   * experience network I/O timeouts. SHould only be called when other
   * potential long running operations are performed.
   * @param client
   * @param monitor
   * @return
   */
  protected synchronized CloudApplication getOrDeployCaldecottApp(IProgressMonitor monitor,
      CloudFoundryOperations client) throws CoreException {
    monitor.setTaskName("Obtaining tunnel application"); //$NON-NLS-1$
    CloudApplication caldecottApp = null;
    try {
      caldecottApp = getCaldecottApp(client);
    }
    catch (Throwable e) {
      // Ignore all first attempt.
    }

    if (caldecottApp == null) {
      deployCaldecottApp(monitor, client);
    }

    try {
      caldecottApp = client.getApplication(TunnelHelper.getTunnelAppName());
    }
    catch (Throwable e) {
      throw new CoreException(CloudFoundryPlugin.getErrorStatus(e));
    }
    return caldecottApp;
  }

  protected void deployCaldecottApp(IProgressMonitor monitor, CloudFoundryOperations client) throws CoreException {
    monitor.setTaskName("Publishing tunnel application"); //$NON-NLS-1$
    Thread t = Thread.currentThread();
    ClassLoader oldLoader = t.getContextClassLoader();
    boolean deployed = false;
    try {
      t.setContextClassLoader(CloudFoundryServerBehaviour.class.getClassLoader());
      if (client instanceof CloudFoundryClient) {
        TunnelHelper.deployTunnelApp((CloudFoundryClient) client);
        deployed = true;
      }

    }
    catch (TunnelException te) {
      CloudFoundryPlugin.logError(te);
    }
    finally {
      t.setContextClassLoader(oldLoader);
    }

    // refresh the list of modules to create a module for the
    // deployed Caldecott App
    if (deployed) {
      cloudServer.getBehaviour().refreshModules(monitor);
    }

  }

  public synchronized CloudFoundryApplicationModule getCaldecottModule(IProgressMonitor monitor) throws CoreException {
    return cloudServer.getExistingCloudModule(TunnelHelper.getTunnelAppName());
  }
}
TOP

Related Classes of org.cloudfoundry.ide.eclipse.server.core.internal.client.TunnelBehaviour

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.