CycloneTechnology/ChaMP

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

Summary

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

import java.net.InetAddress

import com.cyclone.command.TimeoutContext
import com.cyclone.ipmi.IpmiError.IpmiErrorOr
import com.cyclone.ipmi._
import com.cyclone.ipmi.command.global.DeviceAddress
import com.cyclone.ipmi.command.ipmiMessagingSupport.SetSessionPrivilegeLevel
import com.cyclone.ipmi.protocol.packet.{CommandResultCodec, IpmiCommandResult, IpmiStandardCommand}
import com.cyclone.util.concurrent.Futures
import scalaz.EitherT._
import scalaz.Scalaz._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

/**
  * Low-level IMPI client entry point api.
  */
trait IpmiClient {

  /**
    * Creates a logged out [[IpmiConnection]] for interacting with an IPMI BMC device
    * on the specified address and port.
    */
  def connectionFor(
    inetAddress: InetAddress,
    port: Int = IpmiTarget.defaultPort
  ): Future[IpmiConnection]

  /**
    * Utility to perform a task with a new connection, closing it afterwards
    */
  def withConnection[T](inetAddress: InetAddress, port: Int = IpmiTarget.defaultPort)(
    task: IpmiConnection => Future[T]
  ): Future[T] = {
    val futureConnection = connectionFor(inetAddress, port)

    val result = for {
      connection <- futureConnection
      result     <- task(connection)
    } yield result

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

trait IpmiClientComponent {
  def ipmiClient: IpmiClient
}

/**
  * Representation of an IPMI 'connection' to a single device.
  */
trait IpmiConnection {

  /**
    * Negotiates a session satisfying version and privilege level constraints.
    */
  def negotiateSession(
    ipmiCredentials: IpmiCredentials,
    versionRequirement: IpmiVersionRequirement,
    privilegeLevel: PrivilegeLevel = PrivilegeLevel.User
  )(implicit timeoutContext: TimeoutContext): Future[IpmiErrorOr[Unit]]

  /**
    * Elevates privileges to the required level
    *
    * @param privilegeLevel the new level
    * @return the new level if a level was set or an error
    */
  def elevatePrivilegeLevel(
    privilegeLevel: PrivilegeLevel
  )(implicit timeoutContext: TimeoutContext): Future[IpmiErrorOr[Option[PrivilegeLevel]]] =
    // user level anyway by default
    if (privilegeLevel != PrivilegeLevel.User) {
      val result = for {
        cmdRes <- eitherT(executeCommandOrError(SetSessionPrivilegeLevel.Command(privilegeLevel)))
      } yield Some(cmdRes.newPrivilegeLevel)

      result.run
    } else
      None.right.point[Future]

  /**
    * The connection should be closed down when it is finished with.
    */
  def closedown(): Future[Unit]

  /**
    * If a session has been negotiated, executes the command within that session using the negotiated IPMI version.
    *
    * If no session has been negotiated, attempts to execute the command outside of the session using
    * IMPI V1.5. Only certain commands can be run without the privileges provided with a session.
    *
    * The command will be executed using IPMI V1.5 protocol.
    *
    * Errors are converted to failed futures.
    */
  def executeCommand[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    command: Command,
    targetAddress: DeviceAddress
  )(
    implicit timeoutContext: TimeoutContext,
    codec: CommandResultCodec[Command, Result]
  ): Future[Result] = {
    val raw = executeCommandOrError(command, targetAddress)

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

  def executeCommand[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    command: Command
  )(implicit timeoutContext: TimeoutContext, codec: CommandResultCodec[Command, Result]): Future[Result] =
    executeCommand(command, DeviceAddress.BmcAddress)

  /**
    * If a session has been negotiated, executes the command within that session using the negotiated IPMI version.
    *
    * If no session has been negotiated, attempts to execute the command outside of the session using
    * IMPI V1.5. Only certain commands can be run without the privileges provided with a session.
    *
    * The command will be executed using IPMI V1.5 protocol.
    */
  def executeCommandOrError[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    command: Command,
    targetAddress: DeviceAddress
  )(
    implicit timeoutContext: TimeoutContext,
    codec: CommandResultCodec[Command, Result]
  ): Future[IpmiErrorOr[Result]]

  def executeCommandOrError[Command <: IpmiStandardCommand, Result <: IpmiCommandResult](
    command: Command
  )(
    implicit timeoutContext: TimeoutContext,
    codec: CommandResultCodec[Command, Result]
  ): Future[IpmiErrorOr[Result]] =
    executeCommandOrError(command, DeviceAddress.BmcAddress)
}