Motivation

The main motivation behind this refactor work is to increase the readability/quality of some of the ServerResource interface implementations, like the CitrixResourceBase, LibvirtComputingResource and NiciraNvpResource classes. There are several other implementations of ServerResource, but they won't be tackled on the 4.6.0 release of Apache CloudStack.

In order to fulfil our goal a number of points have to be worked on:

  • Reduce amount of lines in the implementations.
  • Reduce the amount of private methods in the implementations.
  • Increase loosely-coupling of the command classes used by the implementations
  • Increase unit test coverage

In order to give an idea on how the refactor took place for those ServerResource implementation, some extra documentation, including code snippets and diagrams, is available below. Although the diagrams and code snippets use the CitrixResrouceBase as example, the same approach has been successfully applied to LibvirtComputingResource and NiciraNvpResource. Although the latter is not an hypervisor implementation, but a network plugin.

Design

The ServerResource.executeRequest() method expects a Command object, which is defined in the Cloud Core module. Since we could not change this structure, in order to avoid a major change in the whole API layer, we added a CommandWrapper to deal with the behaviour expected by each ServerResourse sub-class. In that sense, the whole XenServer, Libvirt and Nicira implementations are completely independent and don't change anything in the existing commands. Only extensions of the CommandWrapper<T extends Command, A extends Answer, R extends ServerResource> class will be enough to have the commands decoupled from the ServerResource implementation.

Changes in the Cloud Core module

In order to have a smooth refactor, we added the following resources to the Cloud-Core module:

  • CommandWrapper<T extends Command, A extends Answer, R extends ServerResource>
    • An abstract class that will be extended by each CommandWrapper implementation.
    • This class uses Generics in order to cope with implementations of the following objects only
      • Command
      • Answer
      • ServerResource
  • RequestWrapper
    • An abstract class used to hold a map containing the key/value pairs of ServerResources/CommandWrappers.
    • This class is also responsible by retrieving CommandWrappers implementations based on given Commands.
    • Extensions of this class will be responsible by filling the ServerResources/CommandWrappers map.
  • ResourceWrapper
    • Annotation used to make the CommandWrappers. More details in the Reflections & Annotations section.

By using the 3 objects mentioned above we no longer need the 143  if/else block that were part of the 3 refactored classes; which also led to 143 private methods. Instead, we can simply load a command from the wrapper with the snippet below:

 

executeRequest(Command command)
final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance();
try {
    return wrapper.execute(cmd, this);
} catch (final Exception e) {
    return Answer.createUnsupportedCommandAnswer(cmd);
}

The diagram below depicts the relationship between the CitrixResourceBase and the new classes. The same can be applied to LibvirtComputingResource and NiciraNvpResource. Future implementations will follow the same model.

Please keep in mind that It's just an exhibit of what has been done: we did not want to add all the new classes to the diagram.

The image is a bit too large, but one can download the original in order to have a better idea of the implementation.

Reflections & Annotations

In order to keep the maintenance of the ServerResource implementations easy, we also added the use of Reflections & Annotations in the current code. What kind of benefits does it bring?

  • Command Wrappers are now annotated: it makes easier to add a new command, if needed.
  • No code changes are needed in the CitrixRequestWrapper class when a new command is added.
  • All classes annotated with the ResourceWrapper annotation will be automatically loaded when the CitrixRequestWrapper is initialised.

The ResourceWrapper annotation is simple and contains only one attributes: handles. That attribute is used to indicate which Command should be handled by the Wrapper class. Check the annotation code below: 

ResourceWrapper annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceWrapper {
    Class<? extends Command> handles();
}

Loading CommandWrappers annotated with the ResourceWrapper will need three simple steps:

  1. On your RequestWrapper implementation (e.g. CitrixRequestWrapper), do the following:
    1. Load the CommadWrappers that are annotated

      Load annotations
      Reflections baseWrappers = new Reflections("com.cloud.hypervisor.xenserver.resource.wrapper.xenbase")
      Set<Class<? extends CommandWrapper>> baseSet = baseWrappers.getSubTypesOf(CommandWrapper.class);
    2. Fill the resources

      Filling resources
      final Hashtable<Class<? extends Command>, CommandWrapper> citrixCommands = processAnnotations(baseSet);
      resources.put(CitrixResourceBase.class, citrixCommands);

The resources map is defined in the RequestWrapper abstract class.

When defining a new wrapper class for a given Command, we have to follow the following:

  1. The class has to be in a package that is loaded via reflection
  2. The class has to be annotated with the ResourceWrapper annotation

Check the snippet below:

CommandWrapper
@ResourceWrapper(handles =  RebootRouterCommand.class)
public final class CitrixRebootRouterCommandWrapper extends CommandWrapper<RebootRouterCommand, Answer, CitrixResourceBase> {
    ...
}

References

Apache Issues

  • Unable to render Jira issues macro, execution error.
  • Unable to render Jira issues macro, execution error.
  • Unable to render Jira issues macro, execution error.
  • Unable to render Jira issues macro, execution error.

 

 

 

  • No labels

1 Comment

  1. In OpenStack they are using the XAPI host.plugin call to execute commands in the Dom0 side and it can be extensible to connect in the SystemVMs and call shell commands in the Dom0. I started this discussion here:

    http://mail-archives.apache.org/mod_mbox/cloudstack-dev/201411.mbox/%3CD0978DA1.106BC%25marco.sinhoreli@shapeblue.com%3E

    Today only ROOT user is used to connect to the XenServer and it can be increased using the XenServer RBAC to enable non-root users for example.