CycloneTechnology/ChaMP

View on GitHub
champ-ipmi/src/main/scala/com/cyclone/ipmi/Ipmi.scala

Summary

Maintainability
A
0 mins
Test Coverage
package com.cyclone.ipmi

import akka.actor.ActorSystem
import com.cyclone.akka.ActorSystemComponent
import com.cyclone.command.{OperationDeadline, TimeoutContext}
import com.cyclone.ipmi.IpmiError.IpmiErrorOr
import com.cyclone.ipmi.client.{ActorIpmiClientComponent, IpmiClientComponent}
import com.cyclone.ipmi.command.ipmiMessagingSupport.GetChannelAuthenticationCapabilities
import com.cyclone.ipmi.protocol.ExtensionIpmiManagerComponent
import com.cyclone.ipmi.protocol.packet.{CommandResultCodec, IpmiCommandResult, IpmiStandardCommand}
import com.cyclone.ipmi.tool.command.IpmiCommands.CommandExecutor
import com.cyclone.ipmi.tool.command.{IpmiCommands, IpmiToolCommand, IpmiToolCommandResult}
import com.cyclone.util.concurrent.Futures
import scalaz.EitherT._
import scalaz.Scalaz._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration.FiniteDuration
import scala.util.control.NonFatal

/**
  * IPMI API.
  *
  * Allows executing [[IpmiStandardCommand]]s (defined in the IPMI Specification) along with higher level
  * [[IpmiToolCommand]]s (inspired by the popular ipmitool command line interface).
  *
  * @author Jeremy.Stone
  */
trait Ipmi {

  /**
    * Executes an [[IpmiStandardCommand]].
    *
    * Errors are converted to failed futures.
    */
  def executeCommand[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    target: IpmiTarget,
    command: Command
  )(
    implicit timeoutContext: TimeoutContext,
    codec: CommandResultCodec[Command, Result]
  ): Future[Result] = {
    val raw = executeCommandOrError(target, command)

    Futures.disjunctionToFailedFuture(raw)(IpmiError.toThrowable)
  }

  /**
    * Executes an [[IpmiStandardCommand]] for a connection the result as a disjunction
    */
  def executeCommand[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](command: Command)(
    implicit codec: CommandResultCodec[Command, Result],
    context: IpmiOperationContext
  ): Future[Result] = {
    val raw = executeCommandOrError(command)

    Futures.disjunctionToFailedFuture(raw)(IpmiError.toThrowable)
  }

  /**
    * Executes an [[IpmiStandardCommand]] returning the result as a disjunction
    */
  def executeCommandOrError[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    target: IpmiTarget,
    command: Command
  )(
    implicit timeoutContext: TimeoutContext,
    codec: CommandResultCodec[Command, Result]
  ): Future[IpmiErrorOr[Result]]

  /**
    * Executes an [[IpmiStandardCommand]]
    *
    * Errors are converted to failed futures.
    */
  def executeCommandOrError[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    command: Command
  )(
    implicit codec: CommandResultCodec[Command, Result],
    context: IpmiOperationContext
  ): Future[IpmiErrorOr[Result]]

  /**
    * Executes an [[IpmiToolCommand]].
    *
    * Errors are converted to failed futures.
    */
  def executeToolCommand[Command <: IpmiToolCommand, Result <: IpmiToolCommandResult](
    target: IpmiTarget,
    command: Command
  )(
    implicit timeoutContext: TimeoutContext,
    executor: CommandExecutor[Command, Result]
  ): Future[Result] = {
    val raw = executeToolCommandOrError(target, command)

    Futures.disjunctionToFailedFuture(raw)(IpmiError.toThrowable)
  }

  /**
    * Executes an [[IpmiToolCommand]] for a connection the result as a disjunction
    */
  def executeToolCommand[Command <: IpmiToolCommand, Result <: IpmiToolCommandResult](command: Command)(
    implicit executor: CommandExecutor[Command, Result],
    context: IpmiOperationContext
  ): Future[Result] = {
    val raw = executeToolCommandOrError(command)

    Futures.disjunctionToFailedFuture(raw)(IpmiError.toThrowable)
  }

  /**
    * Executes an [[IpmiToolCommand]] returning the result as a disjunction
    */
  def executeToolCommandOrError[Command <: IpmiToolCommand, Result <: IpmiToolCommandResult](
    target: IpmiTarget,
    command: Command
  )(
    implicit timeoutContext: TimeoutContext,
    executor: CommandExecutor[Command, Result]
  ): Future[IpmiErrorOr[Result]]

  /**
    * Executes an [[IpmiToolCommand]]
    *
    * Errors are converted to failed futures.
    */
  def executeToolCommandOrError[Command <: IpmiToolCommand, Result <: IpmiToolCommandResult](
    command: Command
  )(
    implicit executor: CommandExecutor[Command, Result],
    context: IpmiOperationContext
  ): Future[IpmiErrorOr[Result]]

  /**
    * Utility to perform some operation (e.q. sequence of commands) with the same context.
    */
  def withContextOrError[T](target: IpmiTarget)(
    operation: IpmiOperationContext => Future[IpmiErrorOr[T]]
  )(implicit timeoutContext: TimeoutContext): Future[IpmiErrorOr[T]]

  /**
    * Utility to perform some operation (e.q. sequence of commands) with the same context.
    */
  def withContext[T](target: IpmiTarget)(
    operation: IpmiOperationContext => Future[T]
  )(implicit timeoutContext: TimeoutContext): Future[T] = {
    val raw = withContextOrError(target)(ctx => rightT(operation(ctx)).run)

    Futures.disjunctionToFailedFuture(raw)(IpmiError.toThrowable)
  }

  /**
    * Tests IPMI support for an address (ignores credentials).
    */
  def testSupport(target: IpmiTarget, timeout: FiniteDuration): Future[Boolean]

  /**
    * Tests whether an authenticated session can be negotiated for an address
    * based on the credentials in the target.
    */
  def testNegotiateSession(target: IpmiTarget, timeout: FiniteDuration): Future[IpmiErrorOr[Unit]]
}

object Ipmi {

  /**
    * Creates an [[Ipmi]].
    *
    * The resulting object can be used for multiple command executions etc.
    *
    * @param system an actor system
    */
  def create(implicit system: ActorSystem): Ipmi = {
    val component = new DefaultIpmiComponent with ActorIpmiClientComponent with ExtensionIpmiManagerComponent
    with ActorSystemComponent {
      implicit def actorSystem: ActorSystem = system
    }

    component.ipmi
  }
}

trait IpmiComponent {
  def ipmi: Ipmi
}

trait DefaultIpmiComponent extends IpmiComponent {
  self: IpmiClientComponent =>

  lazy val ipmi: Ipmi = new Ipmi {

    def executeCommandOrError[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
      target: IpmiTarget,
      command: Command
    )(
      implicit timeoutContext: TimeoutContext,
      codec: CommandResultCodec[Command, Result]
    ): Future[IpmiError.IpmiErrorOr[Result]] =
      withContextOrError(target)(implicit context => context.connection.executeCommandOrError(command))

    def executeCommandOrError[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](command: Command)(
      implicit codec: CommandResultCodec[Command, Result],
      context: IpmiOperationContext
    ): Future[IpmiErrorOr[Result]] = {
      implicit val to: TimeoutContext = context.timeoutContext
      context.connection.executeCommandOrError(command)
    }

    def executeToolCommandOrError[Command <: IpmiToolCommand, Res <: IpmiToolCommandResult](
      target: IpmiTarget,
      command: Command
    )(
      implicit timeoutContext: TimeoutContext,
      executor: CommandExecutor[Command, Res]
    ): Future[IpmiErrorOr[Res]] = withContextOrError(target)(implicit context => executor.execute(command))

    def executeToolCommandOrError[Command <: IpmiToolCommand, Result <: IpmiToolCommandResult](
      command: Command
    )(
      implicit executor: IpmiCommands.CommandExecutor[Command, Result],
      context: IpmiOperationContext
    ): Future[IpmiErrorOr[Result]] = executor.execute(command)

    def withContextOrError[T](target: IpmiTarget)(
      operation: IpmiOperationContext => Future[IpmiErrorOr[T]]
    )(implicit timeoutContext: TimeoutContext): Future[IpmiErrorOr[T]] = {
      target match {
        case lan: IpmiTarget.LAN =>
          import lan._
          val futureConnection = ipmiClient.connectionFor(inetAddress, port)

          val result = for {
            connection <- rightT(futureConnection)
            _          <- eitherT(connection.negotiateSession(credentials, versionRequirement, privilegeLevel))
            result     <- eitherT(operation(IpmiOperationContext(connection, timeoutContext)))
          } yield result

          result.run.andThen {
            case _ => futureConnection.foreach(_.closedown())
          }
      }
    }

    def testSupport(target: IpmiTarget, timeout: FiniteDuration): Future[Boolean] = {
      implicit val timeoutContext: TimeoutContext = TimeoutContext(OperationDeadline.fromNow(timeout))

      target match {
        case lan: IpmiTarget.LAN =>
          import lan._

          ipmiClient.withConnection(inetAddress, port) { connection =>
            // Spec says can use GetChannelAuthenticationCapabilities to discover support...
            val result =
              connection.executeCommandOrError(GetChannelAuthenticationCapabilities.Command(PrivilegeLevel.User))

            // Don't care what the result is just whether we get a good one...
            result.map(_.isRight).recover { case NonFatal(_) => false }
          }
      }
    }

    def testNegotiateSession(
      target: IpmiTarget,
      timeout: FiniteDuration
    ): Future[IpmiErrorOr[Unit]] = {
      implicit val timeoutContext: TimeoutContext = TimeoutContext(OperationDeadline.fromNow(timeout))

      target match {
        case lan: IpmiTarget.LAN =>
          import lan._

          ipmiClient.withConnection(inetAddress, port) { connection =>
            val result =
              connection.negotiateSession(credentials, versionRequirement, PrivilegeLevel.User)

            result.recover { case NonFatal(e) => IpmiExceptionError(e).left }
          }
      }
    }
  }
}