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

io.scalecube.cluster.metadata.MetadataStoreImpl Maven / Gradle / Ivy

The newest version!
package io.scalecube.cluster.metadata;

import io.scalecube.cluster.ClusterConfig;
import io.scalecube.cluster.Member;
import io.scalecube.cluster.transport.api.Message;
import io.scalecube.cluster.transport.api.Transport;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;

public class MetadataStoreImpl implements MetadataStore {

  private static final Logger LOGGER = LoggerFactory.getLogger(MetadataStore.class);

  // Qualifiers

  public static final String GET_METADATA_REQ = "sc/metadata/req";
  public static final String GET_METADATA_RESP = "sc/metadata/resp";

  public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);

  // Injected

  private Object localMetadata;
  private final Member localMember;
  private final Transport transport;
  private final ClusterConfig config;

  // State

  private final Map membersMetadata = new HashMap<>();

  // Scheduler

  private final Scheduler scheduler;

  // Disposables

  private final Disposable.Composite actionsDisposables = Disposables.composite();

  /**
   * Constructor.
   *
   * @param localMember local member
   * @param transport transport
   * @param localMetadata local metadata (optional)
   * @param config config
   * @param scheduler scheduler
   */
  public MetadataStoreImpl(
      Member localMember,
      Transport transport,
      Object localMetadata,
      ClusterConfig config,
      Scheduler scheduler) {
    this.localMember = Objects.requireNonNull(localMember);
    this.transport = Objects.requireNonNull(transport);
    this.config = Objects.requireNonNull(config);
    this.scheduler = Objects.requireNonNull(scheduler);
    this.localMetadata = localMetadata; // optional
  }

  @Override
  public void start() {
    // Subscribe
    actionsDisposables.add(
        // Listen to incoming get_metadata requests from other members
        transport
            .listen()
            .publishOn(scheduler)
            .subscribe(
                this::onMessage,
                ex -> LOGGER.error("[{}][onMessage][error] cause:", localMember, ex)));
  }

  @Override
  public void stop() {
    actionsDisposables.dispose();
    membersMetadata.clear();
  }

  @Override
  public  Optional metadata() {
    //noinspection unchecked
    return Optional.ofNullable((T) localMetadata);
  }

  @Override
  public Optional metadata(Member member) {
    return Optional.ofNullable(membersMetadata.get(member)).map(ByteBuffer::slice);
  }

  @Override
  public void updateMetadata(Object metadata) {
    localMetadata = metadata;
  }

  @Override
  public ByteBuffer updateMetadata(Member member, ByteBuffer metadata) {
    if (localMember.equals(member)) {
      throw new IllegalArgumentException("removeMetadata must not accept local member");
    }

    ByteBuffer value = metadata.slice();
    ByteBuffer result = membersMetadata.put(member, value);

    if (result == null) {
      LOGGER.debug(
          "[{}] Added metadata(size={}) for member {}", localMember, value.remaining(), member);
    } else {
      LOGGER.debug(
          "[{}] Updated metadata(size={}) for member {}", localMember, value.remaining(), member);
    }
    return result;
  }

  @Override
  public ByteBuffer removeMetadata(Member member) {
    if (localMember.equals(member)) {
      throw new IllegalArgumentException("removeMetadata must not accept local member");
    }
    // remove
    ByteBuffer metadata = membersMetadata.remove(member);
    if (metadata != null) {
      LOGGER.debug(
          "[{}] Removed metadata(size={}) for member {}",
          localMember,
          metadata.remaining(),
          member);
      return metadata;
    }
    return null;
  }

  @Override
  public Mono fetchMetadata(Member member) {
    return Mono.defer(
        () -> {
          final String cid = UUID.randomUUID().toString();
          final String targetAddress = member.address();

          LOGGER.debug("[{}][{}] Getting metadata for member {}", localMember, cid, member);

          Message request =
              Message.builder()
                  .qualifier(GET_METADATA_REQ)
                  .correlationId(cid)
                  .data(new GetMetadataRequest(member))
                  .build();

          return transport
              .requestResponse(targetAddress, request)
              .timeout(Duration.ofMillis(config.metadataTimeout()), scheduler)
              .publishOn(scheduler)
              .doOnSuccess(
                  s ->
                      LOGGER.debug(
                          "[{}][{}] Received GetMetadataResp from {}",
                          localMember,
                          cid,
                          targetAddress))
              .map(Message::data)
              .map(GetMetadataResponse::getMetadata)
              .doOnError(
                  th ->
                      LOGGER.warn(
                          "[{}][{}] Timeout getting GetMetadataResp "
                              + "from {} within {}ms, cause: {}",
                          localMember,
                          cid,
                          targetAddress,
                          config.metadataTimeout(),
                          th.toString()));
        });
  }

  // ================================================
  // ============== Event Listeners =================
  // ================================================

  private void onMessage(Message message) {
    if (GET_METADATA_REQ.equals(message.qualifier())) {
      onMetadataRequest(message);
    }
  }

  private void onMetadataRequest(Message message) {
    final String sender = message.sender();
    LOGGER.debug("[{}] Received GetMetadataReq from {}", localMember, sender);

    GetMetadataRequest reqData = message.data();
    Member targetMember = reqData.getMember();

    // Validate target member
    if (!targetMember.id().equals(localMember.id())) {
      LOGGER.warn(
          "[{}] Received GetMetadataReq from {} to {}, but local member is {}",
          localMember,
          sender,
          targetMember,
          localMember);
      return;
    }

    // Prepare response
    GetMetadataResponse respData = new GetMetadataResponse(localMember, encodeMetadata());

    Message response =
        Message.builder()
            .qualifier(GET_METADATA_RESP)
            .correlationId(message.correlationId())
            .data(respData)
            .build();

    LOGGER.debug("[{}] Send GetMetadataResp to {}", localMember, sender);
    transport
        .send(sender, response)
        .subscribe(
            null,
            ex ->
                LOGGER.debug(
                    "[{}] Failed to send GetMetadataResp to {}, cause: {}",
                    localMember,
                    sender,
                    ex.toString()));
  }

  private ByteBuffer encodeMetadata() {
    ByteBuffer result = null;
    try {
      result = config.metadataCodec().serialize(localMetadata);
    } catch (Exception e) {
      LOGGER.error(
          "[{}] Failed to encode metadata: {}, cause: {}",
          localMember,
          localMetadata,
          e.toString());
    }
    return Optional.ofNullable(result).orElse(EMPTY_BUFFER);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy