All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avsystem.commons.redis.commands.cluster.scala Maven / Gradle / Ivy

package com.avsystem.commons
package redis.commands

import com.avsystem.commons.misc.{Opt => _, OptArg => _, _}
import com.avsystem.commons.redis.CommandEncoder.CommandArg
import com.avsystem.commons.redis._
import com.avsystem.commons.redis.commands.ReplyDecoders._
import com.avsystem.commons.redis.protocol.SimpleStringMsg

import scala.collection.compat._
import scala.collection.mutable

trait KeyedClusterApi extends ApiSubset {
  def keySlot(key: Key): Int =
    Hash.slot(keyCodec.write(key))

  /** Executes [[http://redis.io/commands/cluster-keyslot CLUSTER KEYSLOT]] */
  def clusterKeyslot(key: Key): Result[Int] =
    execute(new ClusterKeyslot(key))

  private final class ClusterKeyslot(key: Key) extends RedisIntCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "KEYSLOT").key(key).result
  }
}

trait NodeClusterApi extends KeyedClusterApi {
  /** Executes [[http://redis.io/commands/cluster-addslots CLUSTER ADDSLOTS]] */
  def clusterAddslots(slot: Int, slots: Int*): Result[Unit] =
    execute(new ClusterAddslots(slot +:: slots))

  /** Executes [[http://redis.io/commands/cluster-addslots CLUSTER ADDSLOTS]]
    * or does nothing when `slots` is empty. */
  def clusterAddslots(slots: Iterable[Int]): Result[Unit] =
    execute(new ClusterAddslots(slots))

  /** Executes [[http://redis.io/commands/cluster-bumpepoch CLUSTER BUMPEPOCH]] */
  def clusterBumpepoch: Result[BumpepochResult] =
    execute(ClusterBumpepoch)

  /** Executes [[http://redis.io/commands/cluster-count-failure-reports CLUSTER COUNT-FAILURE-REPORTS]] */
  def clusterCountFailureReports(nodeId: NodeId): Result[Long] =
    execute(new ClusterCountFailureReports(nodeId))

  /** Executes [[http://redis.io/commands/cluster-countkeysinslot CLUSTER COUNTKEYSINSLOT]] */
  def clusterCountkeysinslot(slot: Int): Result[Long] =
    execute(new ClusterCountkeysinslot(slot))

  /** Executes [[http://redis.io/commands/cluster-delslots CLUSTER DELSLOTS]] */
  def clusterDelslots(slot: Int, slots: Int*): Result[Unit] =
    execute(new ClusterDelslots(slot +:: slots))

  /** Executes [[http://redis.io/commands/cluster-delslots CLUSTER DELSLOTS]]
    * or does nothing when `slots` is empty */
  def clusterDelslots(slots: Iterable[Int]): Result[Unit] =
    execute(new ClusterDelslots(slots))

  /** Executes [[http://redis.io/commands/cluster-failover CLUSTER FAILOVER]] */
  def clusterFailover: Result[Unit] = clusterFailover()

  /** Executes [[http://redis.io/commands/cluster-failover CLUSTER FAILOVER]] */
  def clusterFailover(option: OptArg[FailoverOption] = OptArg.Empty): Result[Unit] =
    execute(new ClusterFailover(option.toOpt))

  /** Executes [[http://redis.io/commands/cluster-flushslots CLUSTER FLUSHSLOTS]] */
  def clusterFlushslots: Result[Unit] =
    execute(ClusterFlushslots)

  /** Executes [[http://redis.io/commands/cluster-forget CLUSTER FORGET]] */
  def clusterForget(nodeId: NodeId): Result[Unit] =
    execute(new ClusterForget(nodeId))

  /** Executes [[http://redis.io/commands/cluster-getkeysinslot CLUSTER GETKEYSINSLOT]] */
  def clusterGetkeysinslot(slot: Int, count: Int): Result[Seq[Key]] =
    execute(new ClusterGetkeysinslot(slot, count))

  /** Executes [[http://redis.io/commands/cluster-info CLUSTER INFO]] */
  def clusterInfo: Result[ClusterStateInfo] =
    execute(ClusterInfo)

  /** Executes [[http://redis.io/commands/cluster-meet CLUSTER MEET]] */
  def clusterMeet(address: NodeAddress): Result[Unit] =
    execute(new ClusterMeet(address))

  /** Executes [[http://redis.io/commands/cluster-myid CLUSTER MYID]] */
  def clusterMyid: Result[NodeId] =
    execute(ClusterMyid)

  /** Executes [[http://redis.io/commands/cluster-nodes CLUSTER NODES]] */
  def clusterNodes: Result[Seq[NodeInfo]] =
    execute(ClusterNodes)

  /** Executes [[http://redis.io/commands/cluster-replicas CLUSTER REPLICAS]] */
  def clusterReplicas(nodeId: NodeId): Result[Seq[NodeInfo]] =
    execute(new ClusterReplicas(nodeId))

  /** Executes [[http://redis.io/commands/cluster-replicate CLUSTER REPLICATE]] */
  def clusterReplicate(nodeId: NodeId): Result[Unit] =
    execute(new ClusterReplicate(nodeId))

  /** Executes [[http://redis.io/commands/cluster-reset CLUSTER RESET]] */
  def clusterReset: Result[Unit] = clusterReset()

  /** Executes [[http://redis.io/commands/cluster-reset CLUSTER RESET]] */
  def clusterReset(hard: Boolean = false): Result[Unit] =
    execute(new ClusterReset(hard))

  /** Executes [[http://redis.io/commands/cluster-saveconfig CLUSTER SAVECONFIG]] */
  def clusterSaveconfig: Result[Unit] =
    execute(ClusterSaveconfig)

  /** Executes [[http://redis.io/commands/cluster-set-config-epoch CLUSTER SET-CONFIG-EPOCH]] */
  def clusterSetConfigEpoch(configEpoch: Long): Result[Unit] =
    execute(new ClusterSetConfigEpoch(configEpoch))

  /** Executes [[http://redis.io/commands/cluster-setslot CLUSTER SETSLOT]] */
  def clusterSetslot(slot: Int, subcommand: SetslotCmd): Result[Unit] =
    execute(new ClusterSetslot(slot, subcommand))

  /** Executes [[http://redis.io/commands/cluster-slaves CLUSTER SLAVES]] */
  def clusterSlaves(nodeId: NodeId): Result[Seq[NodeInfo]] =
    execute(new ClusterSlaves(nodeId))

  /** Executes [[http://redis.io/commands/cluster-slots CLUSTER SLOTS]] */
  def clusterSlots: Result[Seq[SlotRangeMapping]] =
    execute(ClusterSlots)

  private final class ClusterAddslots(slots: Iterable[Int]) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "ADDSLOTS").add(slots).result
    override def immediateResult: Opt[Unit] = whenEmpty(slots, ())
  }

  private object ClusterBumpepoch extends AbstractRedisCommand[BumpepochResult](simpleBumpepochResult) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "BUMPEPOCH").result
  }

  private final class ClusterCountFailureReports(nodeId: NodeId) extends RedisLongCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "COUNT-FAILURE-REPORTS").add(nodeId.raw).result
  }

  private final class ClusterCountkeysinslot(slot: Int) extends RedisLongCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "COUNTKEYSINSLOT").add(slot).result
  }

  private final class ClusterDelslots(slots: Iterable[Int]) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "DELSLOTS").add(slots).result
    override def immediateResult: Opt[Unit] = whenEmpty(slots, ())
  }

  private final class ClusterFailover(option: Opt[FailoverOption]) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "FAILOVER").optAdd(option).result
  }

  private object ClusterFlushslots extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "FLUSHSLOTS").result
  }

  private final class ClusterForget(nodeId: NodeId) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "FORGET").add(nodeId.raw).result
  }

  private final class ClusterGetkeysinslot(slot: Int, count: Int) extends RedisDataSeqCommand[Key] with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "GETKEYSINSLOT").add(slot).add(count).result
  }

  private object ClusterInfo
    extends AbstractRedisCommand[ClusterStateInfo](bulk(bs => ClusterStateInfo(bs.utf8String))) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "INFO").result
  }

  private final class ClusterMeet(address: NodeAddress) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "MEET").add(address.ip).add(address.port).result
  }

  private final object ClusterMyid extends AbstractRedisCommand[NodeId](bulkAsNodeId) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "MYID").result
  }

  private object ClusterNodes extends AbstractRedisCommand[Seq[NodeInfo]](bulkAsNodeInfos) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "NODES").result
  }

  private final class ClusterReplicas(nodeId: NodeId) extends AbstractRedisCommand[Seq[NodeInfo]](multiBulkAsNodeInfos) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "REPLICAS").add(nodeId.raw).result
  }

  private final class ClusterReplicate(nodeId: NodeId) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "REPLICATE").add(nodeId.raw).result
  }

  private final class ClusterReset(hard: Boolean) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "RESET").addFlag("HARD", hard).result
  }

  private object ClusterSaveconfig extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "SAVECONFIG").result
  }

  private final class ClusterSetConfigEpoch(configEpoch: Long) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "SET-CONFIG-EPOCH").add(configEpoch).result
  }

  private final class ClusterSetslot(slot: Int, subcommand: SetslotCmd) extends RedisUnitCommand with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "SETSLOT").add(slot).add(subcommand).result
  }

  private final class ClusterSlaves(nodeId: NodeId) extends AbstractRedisCommand[Seq[NodeInfo]](multiBulkAsNodeInfos) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "SLAVES").add(nodeId.raw).result
  }

  private object ClusterSlots
    extends RedisSeqCommand[SlotRangeMapping](multiBulkAsSlotRangeMapping) with NodeCommand {
    val encoded: Encoded = encoder("CLUSTER", "SLOTS").result
  }
}

trait ConnectionClusterApi extends NodeClusterApi {
  /** Executes [[http://redis.io/commands/readonly READONLY]] */
  def readonly: Result[Unit] =
    execute(Readonly)
  /** Executes [[http://redis.io/commands/readwrite READWRITE]] */
  def readwrite: Result[Unit] =
    execute(Readwrite)

  private object Readonly extends RedisUnitCommand with ConnectionCommand {
    val encoded: Encoded = encoder("READONLY").result
  }

  private object Readwrite extends RedisUnitCommand with ConnectionCommand {
    val encoded: Encoded = encoder("READWRITE").result
  }
}

case object Asking extends UnsafeCommand {
  val encoded: Encoded = encoder("ASKING").result
}

case class NodeId(raw: String) extends AnyVal

sealed abstract class FailoverOption(val name: String) extends NamedEnum
object FailoverOption extends NamedEnumCompanion[FailoverOption] {
  case object Force extends FailoverOption("FORCE")
  case object Takeover extends FailoverOption("TAKEOVER")

  val values: List[FailoverOption] = caseObjects
}

sealed trait SetslotCmd
object SetslotCmd {
  case class Migrating(destinationNodeId: NodeId) extends SetslotCmd
  case class Importing(sourceNodeId: NodeId) extends SetslotCmd
  case object Stable extends SetslotCmd
  case class Node(nodeId: NodeId) extends SetslotCmd

  implicit val SubcommandCommandArg: CommandArg[SetslotCmd] =
    CommandArg((encoder, arg) => arg match {
      case Migrating(NodeId(nodeId)) => encoder.add("MIGRATING").add(nodeId)
      case Importing(NodeId(nodeId)) => encoder.add("IMPORTING").add(nodeId)
      case Stable => encoder.add("STABLE")
      case Node(NodeId(nodeId)) => encoder.add("NODE").add(nodeId)
    })
}

case class ClusterStateInfo(info: String) extends ParsedInfo(info, "\r\n", ":") {
  val stateOk: Boolean = attrMap("cluster_state") == "ok"
  val slotsAssigned: Int = attrMap("cluster_slots_assigned").toInt
  val slotsOk: Int = attrMap("cluster_slots_ok").toInt
  val slotsPfail: Int = attrMap("cluster_slots_pfail").toInt
  val slotsFail: Int = attrMap("cluster_slots_fail").toInt
  val knownNodes: Int = attrMap("cluster_known_nodes").toInt
  val size: Int = attrMap("cluster_size").toInt
  val currentEpoch: Long = attrMap("cluster_current_epoch").toLong
  val myEpoch: Long = attrMap("cluster_my_epoch").toLong
  val statsMessagesSent: Long = attrMap("cluster_stats_messages_sent").toLong
  val statsMessagesReceived: Long = attrMap("cluster_stats_messages_received").toLong
}

case class NodeInfo(infoLine: String) {
  private val splitLine: Array[String] = infoLine.split(' ')
  private val splitAddr: Array[String] = splitLine(1).split('@')

  val id: NodeId = NodeId(splitLine(0))
  val address: NodeAddress = NodeAddress.parse(splitAddr(0))
  val clusterPort: Opt[String] = splitAddr.opt.filter(_.length > 1).map(_.apply(1))
  val flags: NodeFlags = NodeFlags(splitLine(2))
  val master: Opt[NodeId] = Opt(splitLine(3)).filter(_ != "-").map(NodeId)
  val pingSent: Long = splitLine(4).toLong
  val pongRecv: Long = splitLine(5).toLong
  val configEpoch: Long = splitLine(6).toLong
  val connected: Boolean = splitLine(7) == "connected"

  val (slots: Seq[SlotRange], importingSlots: Seq[(Int, NodeId)], migratingSlots: Seq[(Int, NodeId)]) = {
    val slots = mutable.ArrayBuilder.make[SlotRange]
    val importingSlots = mutable.ArrayBuilder.make[(Int, NodeId)]
    val migratingSlots = mutable.ArrayBuilder.make[(Int, NodeId)]

    splitLine.iterator.drop(8).foreach { str =>
      (str.indexOf("-<-"), str.indexOf("->-"), str.indexOf('-')) match {
        case (-1, -1, -1) =>
          val slot = str.toInt
          slots += SlotRange(slot, slot)
        case (-1, -1, idx) =>
          slots += SlotRange(str.take(idx).toInt, str.drop(idx + 1).toInt)
        case (idx, -1, _) =>
          importingSlots += ((str.substring(1, idx).toInt, NodeId(str.substring(idx + 1, str.length - 1))))
        case (-1, idx, _) =>
          migratingSlots += ((str.substring(1, idx).toInt, NodeId(str.substring(idx + 1, str.length - 1))))
        case _ =>
      }
    }

    def res[T](b: mutable.ArrayBuilder[T]): IndexedSeq[T] =
      IArraySeq.unsafeWrapArray(b.result())

    (res(slots), res(importingSlots), res(migratingSlots))
  }

  override def toString: String = infoLine
}

class NodeFlags(val raw: Int) extends AnyVal {

  import NodeFlags._

  def |(other: NodeFlags): NodeFlags = new NodeFlags(raw | other.raw)
  def &(other: NodeFlags): NodeFlags = new NodeFlags(raw & other.raw)
  def ^(other: NodeFlags): NodeFlags = new NodeFlags(raw ^ other.raw)
  def unary_~ : NodeFlags = new NodeFlags(~raw)

  def myself: Boolean = (this & Myself) != Noflags
  def master: Boolean = (this & Master) != Noflags
  def slave: Boolean = (this & Slave) != Noflags
  def pfail: Boolean = (this & Pfail) != Noflags
  def fail: Boolean = (this & Fail) != Noflags
  def handshake: Boolean = (this & Handshake) != Noflags
  def noaddr: Boolean = (this & Noaddr) != Noflags

  override def toString: String =
    if (this == Noflags) "noflags"
    else reprValuePairs.iterator
      .collect({ case (str, flags) if (this & flags) != Noflags => str })
      .mkString(",")
}

object NodeFlags {
  val Noflags = new NodeFlags(0)
  val Myself = new NodeFlags(1 << 0)
  val Master = new NodeFlags(1 << 1)
  val Slave = new NodeFlags(1 << 2)
  val Pfail = new NodeFlags(1 << 3)
  val Fail = new NodeFlags(1 << 4)
  val Handshake = new NodeFlags(1 << 5)
  val Noaddr = new NodeFlags(1 << 6)

  private val reprValuePairs = Seq(
    "myself" -> Myself,
    "master" -> Master,
    "slave" -> Slave,
    "fail?" -> Pfail,
    "fail" -> Fail,
    "handshake" -> Handshake,
    "noaddr" -> Noaddr
  )

  def apply(str: String): NodeFlags = {
    val flagSet = str.split(',').to(mutable.HashSet)
    reprValuePairs.foldLeft(Noflags) {
      case (res, (s, flags)) => if (flagSet(s)) res | flags else res
    }
  }
}

case class SlotRangeMapping(
  range: SlotRange, master: NodeAddress, masterId: Opt[NodeId], slaves: Seq[(NodeAddress, Opt[NodeId])]
) {
  private def nodeRepr(addr: NodeAddress, idOpt: Opt[NodeId]): String =
    addr.toString + idOpt.fold("")(id => s" (${id.raw})")
  override def toString: String =
    s"slots: $range, master: ${nodeRepr(master, masterId)}, slaves: ${slaves.map((nodeRepr _).tupled).mkString(",")}"
}
case class SlotRange(start: Int, end: Int) {
  def toRange: Range = start to end
  def contains(slot: Int): Boolean = slot >= start && slot <= end
  override def toString: String = if (start == end) start.toString else s"$start-$end"
}
object SlotRange {
  final val LastSlot = Hash.TotalSlots - 1
  final val Full = SlotRange(0, LastSlot)
}

final class BumpepochResult(implicit enumCtx: EnumCtx) extends AbstractValueEnum {
  val encoded: SimpleStringMsg = SimpleStringMsg(name.toUpperCase)
}
object BumpepochResult extends AbstractValueEnumCompanion[BumpepochResult] {
  final val Bumped, Still: Value = new BumpepochResult
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy