Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JBIDE-21625] Enable debug mode for OpenShift 3 server adapter #1057

Merged
merged 1 commit into from
Mar 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion plugins/org.jboss.tools.openshift.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ Require-Bundle: org.jboss.tools.openshift.client;bundle-version="[3.0.0,4.0.0)",
org.apache.commons.lang;bundle-version="2.6.0",
org.eclipse.core.variables,
org.eclipse.jst.j2ee;bundle-version="1.1.850",
org.jboss.tools.runtime.core;bundle-version="3.1.1"
org.jboss.tools.runtime.core;bundle-version="3.1.1",
org.eclipse.jdt.core
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Export-Package: org.jboss.tools.openshift.core;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
Expand All @@ -36,8 +37,10 @@ Export-Package: org.jboss.tools.openshift.core;x-friends:="org.jboss.tools.opens
org.jboss.tools.openshift.core.server.behavior,
org.jboss.tools.openshift.core.util;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
org.jboss.tools.openshift.internal.core;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
org.jboss.tools.openshift.internal.core.models;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
org.jboss.tools.openshift.internal.core.portforwarding;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
org.jboss.tools.openshift.internal.core.preferences;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
org.jboss.tools.openshift.internal.core.server.debug;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui",
org.jboss.tools.openshift.internal.core.util;x-friends:="org.jboss.tools.openshift.test,org.jboss.tools.openshift.ui"
Service-Component: META-INF/connectionFactory.xml

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.osgi.service.prefs.BackingStoreException;

import com.openshift.restclient.ResourceKind;
import com.openshift.restclient.model.IDeploymentConfig;
import com.openshift.restclient.model.IService;
import com.openshift.restclient.model.route.IRoute;

Expand All @@ -48,6 +49,8 @@
*/
public class OpenShiftServerUtils {

private static final String DEPLOYMENT_CONFIG_KEY = "deploymentconfig";

private static final String LIVERELOAD_PORT_KEY = "port";//Key to the port # of the host the LiveReload server need to proxy

public static final String SERVER_PROJECT_QUALIFIER = "org.jboss.tools.openshift.core"; //$NON-NLS-1$
Expand Down Expand Up @@ -327,4 +330,25 @@ public static String getProjectAttribute(String name, String defaultValue, IServ
public static String getProjectAttribute(String name, String defaultValue, IProject project) {
return ServerUtils.getProjectAttribute(name, defaultValue, SERVER_PROJECT_QUALIFIER, project);
}

public static IDeploymentConfig getDeploymentConfig(IServerAttributes attributes) {
IService service = getService(attributes);
if (service == null) {
return null;
}
//TODO use annotations instead of labels
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you able to retrieve this value from the annoations and move the constant to OpenShiftAPIAnnotations

String dcName = service.getPods().stream().filter(p -> p.getLabels().containsKey(DEPLOYMENT_CONFIG_KEY))
.findFirst()
.map(p -> p.getLabels().get(DEPLOYMENT_CONFIG_KEY))
.orElse(null);
if (dcName == null) {
return null;
}
Connection connection = getConnection(attributes);
if (connection == null) {
return null;
}
IDeploymentConfig dc = connection.getResource(ResourceKind.DEPLOYMENT_CONFIG, service.getNamespace(), dcName);
return dc;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,47 @@
*******************************************************************************/
package org.jboss.tools.openshift.core.server.behavior;

import java.io.IOException;
import java.util.Optional;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.launching.SocketUtil;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.ServerUtil;
import org.jboss.ide.eclipse.as.wtp.core.server.behavior.AbstractSubsystemController;
import org.jboss.ide.eclipse.as.wtp.core.server.behavior.ControllableServerBehavior;
import org.jboss.ide.eclipse.as.wtp.core.server.behavior.IControllableServerBehavior;
import org.jboss.ide.eclipse.as.wtp.core.server.behavior.ILaunchServerController;
import org.jboss.ide.eclipse.as.wtp.core.server.behavior.ISubsystemController;
import org.jboss.tools.foundation.core.plugin.log.StatusFactory;
import org.jboss.tools.openshift.core.server.OpenShiftServerUtils;
import org.jboss.tools.openshift.internal.core.OpenShiftCoreActivator;
import org.jboss.tools.openshift.internal.core.portforwarding.PortForwardingUtils;
import org.jboss.tools.openshift.internal.core.server.debug.DebuggingContext;
import org.jboss.tools.openshift.internal.core.server.debug.IDebugListener;
import org.jboss.tools.openshift.internal.core.server.debug.OpenShiftDebugUtils;

import com.openshift.restclient.OpenShiftException;
import com.openshift.restclient.capability.resources.IPortForwardable;
import com.openshift.restclient.capability.resources.IPortForwardable.PortPair;
import com.openshift.restclient.model.IDeploymentConfig;
import com.openshift.restclient.model.IPod;
import com.openshift.restclient.model.IService;

public class OpenShiftLaunchController extends AbstractSubsystemController
implements ISubsystemController, ILaunchServerController {

private static final String DEBUG_MODE = "debug";

/**
* Get access to the ControllableServerBehavior
Expand All @@ -50,22 +68,89 @@ protected static IControllableServerBehavior getServerBehavior(ILaunchConfigurat
@Override
public void launch(ILaunchConfiguration configuration, String mode,
ILaunch launch, IProgressMonitor monitor) throws CoreException {
IControllableServerBehavior beh = getServerBehavior(configuration);
if( beh != null ) {
((ControllableServerBehavior)beh).setServerStarting();
int state = pollState();
if( state == IServer.STATE_STARTED) {
((ControllableServerBehavior)beh).setServerStarted();
((ControllableServerBehavior)getControllableBehavior()).setRunMode(mode);
} else {
((ControllableServerBehavior)beh).setServerStopped();
((ControllableServerBehavior)getControllableBehavior()).setRunMode(null);
}
IControllableServerBehavior serverBehavior = getServerBehavior(configuration);
if( !(serverBehavior instanceof ControllableServerBehavior )) {
throw toCoreException("Unable to find a IControllableServerBehavior instance");
}
ControllableServerBehavior beh = (ControllableServerBehavior) serverBehavior;

beh.setServerStarting();

IServer server = beh.getServer();

IDeploymentConfig dc = OpenShiftServerUtils.getDeploymentConfig(server);
if (dc == null) {
throw toCoreException("No deployment config was found for "+server.getName());
}

DebuggingContext debugContext = OpenShiftDebugUtils.get().getDebuggingContext(dc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need a null check here in case the DC is not found

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, the OpenShiftDebugUtils#getDebuggingContext(IDeploymentConfig) already checks if the argument is null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, OpenShiftDebugUtils.get().getDebuggingContext(IDeploymentConfig) can return null, which means that a null debugContext will be passed to the startDebugging and stopDebugging methods, which will cause NPEs

if( DEBUG_MODE.equals(mode)) {
startDebugging(server, dc, debugContext, monitor);
} else {//run, profile
stopDebugging(dc, debugContext, monitor);
}

int state = pollState();
if( state == IServer.STATE_STARTED) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need to synchronize on something here since we are evaluating state and then doing something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that piece is @robstryker 's code, I believe he can answer that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to synchronize anything here. It's our job to set the server state here. The IServer is waiting for us to beh.setServerStarted() on success or beh.setServerStopped() on a startup-fail. Since our initial poll indicates it's already up, we set it to started.

beh.setServerStarted();
beh.setRunMode(mode);
} else {
// TODO throw error
beh.setServerStopped();
((ControllableServerBehavior)getControllableBehavior()).setRunMode(null);
}
}


private void startDebugging(IServer server, IDeploymentConfig dc, DebuggingContext debugContext,
IProgressMonitor monitor) throws CoreException {
int remotePort = debugContext.getDebugPort();
if( remotePort == -1 ) {
debugContext.setDebugPort(8787);//TODO get default port from server settings?
}
IDebugListener listener = new IDebugListener() {

public void onDebugChange(DebuggingContext debuggingContext, IProgressMonitor monitor)
throws CoreException {
if (debuggingContext.getPod() == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can debuggingContext be null ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

throw toCoreException("Unable to connect to remote pod");
}
int localPort = mapPortForwarding(debuggingContext, monitor);

if(isJavaProject(server)) {
attachRemoteDebugger(server, localPort, monitor);
}
}

@Override
public void onPodRestart(DebuggingContext debuggingContext, IProgressMonitor monitor)
throws CoreException {
onDebugChange(debuggingContext, monitor);
}
};
debugContext.setDebugListener(listener);
OpenShiftDebugUtils.get().enableDebugMode(dc, debugContext, monitor);
}


private void stopDebugging(IDeploymentConfig dc, DebuggingContext debugContext, IProgressMonitor monitor)
throws CoreException {
IDebugListener listener = new IDebugListener() {

public void onDebugChange(DebuggingContext debuggingContext, IProgressMonitor monitor)
throws CoreException {
//that will automatically kill the remote debugger
unMapPortForwarding(debuggingContext.getPod());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential NPE

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we call the ILaunch#terminate() method instead ?
The ILaunch can be provided by the calling launch(ILaunchConfiguration, String, ILaunch, IProgressMonitor ) method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debuggingContext can't be null here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can certainly improve the detection mechanism. Right now I'd like to merge it as is, so people can use it

}

@Override
public void onPodRestart(DebuggingContext debuggingContext, IProgressMonitor monitor)
throws CoreException {
}
};
debugContext.setDebugListener(listener);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential NPE (see above)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no debugContext is guaranteed to be not null

OpenShiftDebugUtils.get().disableDebugMode(dc, debugContext, monitor);
}

protected int pollState() {
IService service = null;
Exception e = null;
Expand All @@ -92,5 +177,131 @@ public void setupLaunchConfiguration(
IProgressMonitor monitor) throws CoreException {
// Do Nothing
}

private boolean isJavaProject(IServer server) {
IProject p = OpenShiftServerUtils.getDeployProject(server);
try {
return p != null && p.isAccessible() && p.hasNature(JavaCore.NATURE_ID);
} catch (CoreException e) {
OpenShiftCoreActivator.pluginLog().logError(e);
}
return false;
}

/**
* Unmap the port forwarding.
* @throws CoreException
*/
private void unMapPortForwarding(IPod pod) throws CoreException {
if (pod != null) {
try {
PortForwardingUtils.stopPortForwarding(pod, null);
} catch (IOException e) {
throw toCoreException("Unable to stop port forawrding", e);
}
}
}

/**
* Map the remote port to a local port.
* Return the local port in use, or -1 if failed
* @param server
* @param remotePort
* @return the local debug port or -1 if port forwarding did not start or was cancelled.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. or -1 if port forwarding did not start or was cancelled.

private int mapPortForwarding(DebuggingContext debuggingContext, IProgressMonitor monitor) {

monitor.subTask("Enabling port forwarding");
IPod pod = debuggingContext.getPod();
int remotePort = debuggingContext.getDebugPort();
if (pod == null || remotePort < 1) {
//nothing we can do
return -1;
}
Set<PortPair> ports = PortForwardingUtils.getForwardablePorts(pod);
Optional<PortPair> debugPort = ports.stream().filter(p -> remotePort == p.getRemotePort()).findFirst();
if (!debugPort.isPresent() || monitor.isCanceled()) {
return -1;
}

if (PortForwardingUtils.isPortForwardingStarted(pod)) {
int p = debugPort.get().getLocalPort();
return p;
}

for (IPortForwardable.PortPair port : ports) {
if(monitor.isCanceled()) {
return -1;
}
port.setLocalPort(SocketUtil.findFreePort());
monitor.worked(1);
}

PortForwardingUtils.startPortForwarding(pod, ports);

if (PortForwardingUtils.isPortForwardingStarted(pod)) {
int p = debugPort.get().getLocalPort();
return p;
}
//maybe throw exception?
return -1;
}

private void attachRemoteDebugger(IServer server, int localDebugPort, IProgressMonitor monitor) throws CoreException {
monitor.subTask("Attaching remote debugger");

OpenShiftDebugUtils debugUtils = OpenShiftDebugUtils.get();
ILaunchConfiguration debuggerLaunchConfig = debugUtils.getRemoteDebuggerLaunchConfiguration(server);
ILaunchConfigurationWorkingCopy workingCopy;
if (debuggerLaunchConfig == null) {
workingCopy = debugUtils.createRemoteDebuggerLaunchConfiguration(server);
} else {
if (debugUtils.isRunning(debuggerLaunchConfig, localDebugPort)) {
return;
}
workingCopy = debuggerLaunchConfig.getWorkingCopy();
}


IProject project = OpenShiftServerUtils.getDeployProject(server);
debugUtils.setupRemoteDebuggerLaunchConfiguration(workingCopy, project, localDebugPort);
debuggerLaunchConfig = workingCopy.doSave();

long elapsed = 0;
long increment = 1_000;
long maxWait = 60_000; //TODO Get from server settings?
boolean launched = false;
monitor.subTask("Waiting for remote debug port availability");
while(!launched && elapsed < maxWait) {
try {
//TODO That's fugly. ideally we should see if socket on debug port is responsive instead
debuggerLaunchConfig.launch(DEBUG_MODE, new NullProgressMonitor());
launched = true;
} catch (Exception e) {
if (monitor.isCanceled()) {
break;
}
try {
Thread.sleep(increment);
elapsed+=increment;
} catch (InterruptedException ie) {
}
}
}

if (!launched){
throw toCoreException("Unable to start a remote debugger to localhost:"+localDebugPort);
}

monitor.worked(10);
}


private CoreException toCoreException(String msg, Exception e) {
return new CoreException(StatusFactory.errorStatus(OpenShiftCoreActivator.PLUGIN_ID, msg, e));
}

private CoreException toCoreException(String msg) {
return toCoreException(msg, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.openshift.internal.ui.wizard.deployimage;
package org.jboss.tools.openshift.internal.core.models;

import org.apache.commons.lang.StringUtils;
import org.eclipse.osgi.util.NLS;
Expand Down Expand Up @@ -38,7 +38,16 @@ public PortSpecAdapter(String spec){
}
port = Integer.valueOf(parts[0]);
protocol = parts[1].toUpperCase();
name = NLS.bind("{0}-{1}", port, protocol.toLowerCase());
name = getName(port, protocol);
}

public PortSpecAdapter(String name, String protocol, int port){
if(StringUtils.isBlank(protocol)){
throw new IllegalArgumentException("protocol must be set");
}
this.port = port;
this.protocol = protocol;
this.name = (name == null)?getName(port, protocol):name;
}

@Override
Expand Down Expand Up @@ -92,4 +101,8 @@ public boolean equals(Object obj) {



private String getName(int port, String protocol) {
return NLS.bind("{0}-{1}", port, protocol.toLowerCase());
}

}