Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions core/src/com/cloud/resource/RequestWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
import com.cloud.agent.api.Command;

public abstract class RequestWrapper {
static public class CommandNotSupported extends NullPointerException {
public CommandNotSupported(String msg) {
super(msg);
}
public CommandNotSupported(String msg, Throwable cause) {
super(msg);
initCause(cause);
}
}

private static final Logger s_logger = Logger.getLogger(RequestWrapper.class);

Expand All @@ -52,7 +61,7 @@ protected Hashtable<Class<? extends Command>, CommandWrapper> retrieveResource(f

keepResourceClass = keepResourceClass2;
} catch (final ClassCastException e) {
throw new NullPointerException("No key found for '" + command.getClass() + "' in the Map!");
throw new CommandNotSupported("No key found for '" + command.getClass() + "' in the Map!");
}
}
return resource;
Expand All @@ -69,14 +78,14 @@ protected CommandWrapper<Command, Answer, ServerResource> retrieveCommands(final
final Class<? extends Command> commandClass2 = (Class<? extends Command>) keepCommandClass.getSuperclass();

if (commandClass2 == null) {
throw new NullPointerException("All the COMMAND hierarchy tree has been visited but no compliant key has been found for '" + commandClass + "'.");
throw new CommandNotSupported("All the COMMAND hierarchy tree has been visited but no compliant key has been found for '" + commandClass + "'.");
}

commandWrapper = resourceCommands.get(commandClass2);

keepCommandClass = commandClass2;
} catch (final ClassCastException e) {
throw new NullPointerException("No key found for '" + keepCommandClass.getClass() + "' in the Map!");
throw new CommandNotSupported("No key found for '" + keepCommandClass.getClass() + "' in the Map!");
} catch (final NullPointerException e) {
// Will now traverse all the resource hierarchy. Returning null
// is not a problem.
Expand All @@ -102,18 +111,16 @@ protected CommandWrapper<Command, Answer, ServerResource> retryWhenAllFails(fina
final Class<? extends ServerResource> resourceClass2 = (Class<? extends ServerResource>) keepResourceClass.getSuperclass();

if (resourceClass2 == null) {
throw new NullPointerException("All the SERVER-RESOURCE hierarchy tree has been visited but no compliant key has been found for '" + command.getClass() + "'.");
throw new CommandNotSupported("All the SERVER-RESOURCE hierarchy tree has been visited but no compliant key has been found for '" + command.getClass() + "'.");
}

final Hashtable<Class<? extends Command>, CommandWrapper> resourceCommands2 = retrieveResource(command,
(Class<? extends ServerResource>) keepResourceClass.getSuperclass());
keepResourceClass = resourceClass2;

commandWrapper = retrieveCommands(command.getClass(), resourceCommands2);
} catch (final ClassCastException e) {
throw new NullPointerException("No key found for '" + command.getClass() + "' in the Map!");
} catch (final NullPointerException e) {
throw e;
} catch (final ClassCastException | NullPointerException e) {
throw new CommandNotSupported("No key found for '" + command.getClass() + "' in the Map!", e);
}
}
return commandWrapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import com.cloud.resource.RequestWrapper;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
Expand Down Expand Up @@ -1432,13 +1433,21 @@ public boolean stop() {
return true;
}

/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Big 👍 . We really need more of these in our code base :)
Great job!

* This finds a command wrapper to handle the command and executes it.
* If no wrapper is found an {@see UnsupportedAnswer} is sent back.
* Any other exceptions are to be caught and wrapped in an generic {@see Answer}, marked as failed.
*
* @param cmd the instance of a {@see Command} to execute.
* @return the for the {@see Command} appropriate {@see Answer} or {@see UnsupportedAnswer}
*/
@Override
public Answer executeRequest(final Command cmd) {

@rafaelweingartner rafaelweingartner Jun 19, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about a unit test cases? Then, you can have a test case that detects if we catch only RequestWrapper.CommandNotSupported exceptions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that would require throwing everything else. I will add a subset to catch the most obvious cases

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, yes. If you want to check all possible cases. However, you can test with the most obvious ones such as Nullpointer, Exception, CloudRuntimeException, and RuntimeException

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not really convinced that any of these make a lot of difference to one and another. How about the known and unknown case I just added?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It helps. I only think that you could go and test the method retryWhenAllFails instead of using executeRequest. If we want unit tests, we should write them for the units.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we could also unittest RequestWrapper, yes.
executeRequest is the unit here.
higher prios came up, i might revisit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No problems ;)
Thanks anyways for writing at least some tests.


final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
try {
return wrapper.execute(cmd, this);
} catch (final Exception e) {
} catch (final RequestWrapper.CommandNotSupported cmde) {
return Answer.createUnsupportedCommandAnswer(cmd);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public Answer execute(final Command command, final ServerResource serverResource
commandWrapper = retryWhenAllFails(command, resourceClass, resourceCommands);
}

if (commandWrapper == null) {
throw new CommandNotSupported("No way to handle " + command.getClass());
}
return commandWrapper.execute(command, serverResource);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import com.cloud.agent.api.Command;
import com.cloud.agent.api.UnsupportedAnswer;
import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuTuneDef;
import org.apache.commons.lang.SystemUtils;
import org.joda.time.Duration;
Expand Down Expand Up @@ -5191,4 +5193,29 @@ public void testSetQuotaAndPeriodMinQuota() {
Assert.assertEquals(CpuTuneDef.MIN_QUOTA, cpuTuneDef.getQuota());
Assert.assertEquals((int) (CpuTuneDef.MIN_QUOTA / pct), cpuTuneDef.getPeriod());
}

@Test
public void testUnknownCommand() {
libvirtComputingResource = new LibvirtComputingResource();
Command cmd = new Command() {
@Override public boolean executeInSequence() {
return false;
}
};
Answer ans = libvirtComputingResource.executeRequest(cmd);
assertTrue(ans instanceof UnsupportedAnswer);
}

@Test
public void testKnownCommand() {
libvirtComputingResource = new LibvirtComputingResource();
Command cmd = new PingTestCommand() {
@Override public boolean executeInSequence() {
throw new NullPointerException("test succeeded");
}
};
Answer ans = libvirtComputingResource.executeRequest(cmd);
assertFalse(ans instanceof UnsupportedAnswer);
assertTrue(ans instanceof Answer);
}
}