Skip to content

Scala plugin development guide

bugzmanov edited this page Sep 10, 2012 · 14 revisions

Genesis project : Scala plugin development guide

Plugin context

Plugin context is implemented as as spring configuration bean

Plugin context should be annotated with @GenesisPlugin with mandatory id field.

It's main responsibility is to provide step coordination factories and step builder factories to genesis workflow context.
It's advised for plugin context not to export other types of beans to global genesis spring context

@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {

}

Step and step builders

To introduce new steps definition to genesis template dsl one should provide:

  • new step class marked with com.griddynamics.genesis.workflow.Step trait - step definition to be processed by appropriate executors
  • custom com.griddynamics.genesis.plugin.StepBuilder trait implementation - the class is respobsible for collecting attribute values from template and creating Step definition.
  • custom com.griddynamics.genesis.plugin.StepBuilderFactory trait implementaion - the class responsible for connecting StepBuilder with step name representation in template dsl
    Example:

Lets say you want to introduce new ping step with one attibute url:

ping {
      url = "http://google.com"
   }

To support this syntax the followin code is needed:

case class PingStep(url: String) extends Step {
 override val stepDescription = "Ping signal"
}

class PingStepBuilderFactory extends com.griddynamics.genesis.plugin.StepBuilderFactory {
    val stepName = "ping"  //the value should be exactly the same as it's expected to be in template

    override def newStepBuilder = new StepBuilder {
        @BeanProperty var url : String = _  //the name of the variable should be exactly the same as the name of the parameter in template

        def getDetails = new PingStep(url)
    }
}

In order for genesis workflow system to support this new step PingStepBuilderFactory should be exported in plugin context

@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
   @Bean def stepBuilderFactory = new PingStepBuilderFactory
}

Simple step definitions

In case of step builder doesn't having some conversion logic plugin developer can use com.griddynamics.genesis.plugin.StepDefinition class to declare new step without providing new custom StepBuilderFactory and StepBuilder

@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
   @Bean def pingStepDefinition = new StepDefinition("ping", classOf[PingStep])
}

In this case genesis core is capable of instantion of PingStep based on constructor arguments.

Following basic convertions from groovy to scala is supported:

  • value/null (or absense of parameter) to Some(_)/None
  • groovy list(java.lang.List) to scala Seq, Set, List
  • groovy map(java.lang.Map) to scala.Map

Case class default constructor parameters is also supported.

More complex example

Given:

case class ComplexStep(optString: Option[String], defaultInt:Int = 5, list: Seq[String]) extends Step
...
@Bean def complexStepDefinition = new StepDefinition("complexStep", classOf[ComplexStep])

Followin template code:

complexStep {
   list = ["one", "two"]
}

will construct object equal to new ComplexStep(list = Seq(1,2,3))

Actions

Action is the tiniest peace of work workflow engine can execute. Each workflow step can consist of several actions.

class PingAction(url: String) extends Action

Actions have no representations in template dsl. Information of how to execute each action is coded in action executor. 
Information how to split step into actions and coordinate action executors is coded in step coordinator

Action executors

Actions are data objects, to provide appropriate handling of each action one should provide specific action executor. Genesis provide several trait that should be used to implement asynchronous or synchronous executors:

  • ActionExecutor - base executor class
  • AsyncActionExecutor - for async execution
  • SyncActionExecutor - for sync execution
class PingResult(val success: Boolean) extends ActionResult

class PingActionExecutor(val action: PingAction) SyncActionExecutor extends ActionExecutor {
  def startSync() = {
      try {
         new Url(action.url).openConnection()
         new PingResult(true)
      } catch {
         case _ => new PingResult(false)
      }
  }
}

Step coordinator

To coordinate execution of several actions while executing particular step one should implement specific com.griddynamics.genesis.workflow.StepCoordinator

Genesis provide several template classes for different variations, like ActionOrientedStepCoordinator.

Because in this sample step consist of only one action coordinator looks simplified

class PingStepCoordinator(val step: PingStep, context: StepExecutionContext) extends ActionOrientedStepCoordinator {
  var failed = false;

  def getActionExecutor(action: Action) = action match { 
     case a: PingAction => new PingActionExecutor(a)   // in case of step consisting of several actions, several matches will be here
  }

  def getStepResult = {
    GenesisStepResult(context.step, isStepFailed = failed)
  }

  def onActionFinish(result: ActionResult) = {
    result match {
      case a: PingResult => {
        failed = !a.success
        Seq()
      }
      case _: ExecutorThrowable => {
        failed = true
        Seq()
      }
      case _: ExecutorInterrupt => {
        Seq()
      }
    }
  }

  def onStepInterrupt(signal: Signal) = Seq() 

  def onStepStart() = List(new PingAction(step.url))  // list of actions to be executed to finish step. only one action in our case 

}

Step coordinator factory

The last peace of the puzzle is a factory class that should map specific step(constructed from template) to appropriate step coordinator.

To provide such class one should implement com.griddynamics.genesis.plugin.PartialStepCoordinatorFactoryor use one of template classes.

class PingStepCoordinatorFactory extends PartialStepCoordinatorFactory {
  def isDefinedAt(step: Step) = step.isInstanceOf[PingStep]

  def apply(step: Step, context: StepExecutionContext) = {
    new PingStepCoordinator(step.asInstanceOf[PingStep], context)   
  }
}

This implementation should be exported by plugin context

@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
   @Bean def stepBuilderFactory = new PingStepBuilderFactory

   @Bean def stepCoordinator = new PingStepCoordinatorFactory()
}

Steps with single action

In case of step execution does not requiring multiple actions simplified form of step implementation might be used. Only step executor should be implemented (i.e. no step coordinators, no actions, no coordinator factories are required).

Following code is the only code required to implement ping step

case class PingStep(url: String) extends Step

class PingResult(val isStepFailed: Boolean) extends FallibleResult //step result

class PingActionExecutor extends TrivialStepExecutor[PingStep, PingResult] {
  override def execute(step: PingStep, context: StepExecutionContext) = {
      try {
         new Url(step.url).openConnection()
         new PingResult(true)
      } catch {
         case _ => new PingResult(false)
      }
  }
}

@Configuration
@GenesisPlugin(id = "ping", description = "simple ping plugin")
class PingPluginContex {
   @Bean def pingStepDefinition = new StepDefinition("ping", classOf[PingStep])

   @Bean def pingStepExecutor = new PingActionExecutor ()
}

Plugin configuration

Plugin can use genesis main configuration subsystem. To get access to it plugin must conform to following rules:

  • All properties must start with genesis.plugin.<plugin_id>. prefix
  • Plugin should provid genesis-plugin.properties in package root directory with all properties with default values

If the plugin have no mean of runtime reconfiguration it's possible to use spring @Value annotation for easy configuration properties access

@Value("${genesis.plugin.ping.timeout}") var timeout: Int = _

Genesis services

Genesis provide several services that can be discovered and used by plugins. The definition of services can be found in internal-api module in com.griddynamics.genesis.service package.

Discovery of the services is implemented via @Autowired spring feature

@Autowired var sshService: SshService = _
Clone this wiki locally