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

cz.o2.proxima.server.IngestServer Maven / Gradle / Ivy

There is a newer version: 0.14.0
Show newest version
/*
 * Copyright 2017-2023 O2 Czech Republic, a.s.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cz.o2.proxima.server;

import static cz.o2.proxima.server.Constants.CFG_NUM_THREADS;
import static cz.o2.proxima.server.Constants.DEFAULT_NUM_THREADS;

import com.google.common.annotations.VisibleForTesting;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import cz.o2.proxima.direct.core.DirectDataOperator;
import cz.o2.proxima.direct.core.OnlineAttributeWriter;
import cz.o2.proxima.functional.Factory;
import cz.o2.proxima.proto.service.Rpc;
import cz.o2.proxima.repository.AttributeDescriptor;
import cz.o2.proxima.repository.ConfigConstants;
import cz.o2.proxima.repository.Repository;
import cz.o2.proxima.server.metrics.Metrics;
import cz.o2.proxima.server.transaction.TransactionContext;
import cz.o2.proxima.storage.StreamElement;
import io.grpc.BindableService;
import io.grpc.Metadata;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerCall;
import io.grpc.ServerCall.Listener;
import io.grpc.ServerCallHandler;
import java.io.File;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.jodah.failsafe.RetryPolicy;

/** The ingestion server. */
@Slf4j
public class IngestServer {

  /**
   * Run the server.
   *
   * @param args command line arguments
   * @throws Throwable on error
   */
  public static void main(String[] args) throws Throwable {
    runWithServerFactory(() -> new IngestServer(getCfgFromArgs(args)));
  }

  protected static Config getCfgFromArgs(String[] args) {
    return args.length == 0
        ? ConfigFactory.load().resolve()
        : ConfigFactory.parseFile(new File(args[0])).resolve();
  }

  protected static void runWithServerFactory(Factory serverFactory) {
    serverFactory.apply().run();
  }

  @Getter final Executor executor;

  @Getter final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(5);

  @Getter final Repository repo;
  @Getter final DirectDataOperator direct;
  @Getter final Config cfg;
  @Getter final boolean ignoreErrors;
  @Getter @Nullable final TransactionContext transactionContext;

  @Getter
  RetryPolicy retryPolicy =
      new RetryPolicy().withMaxRetries(3).withBackoff(3000, 20000, TimeUnit.MILLISECONDS, 2.0);

  protected IngestServer(Config cfg) {
    this(cfg, false);
  }

  @VisibleForTesting
  IngestServer(Config cfg, boolean test) {
    this(cfg, test ? Repository.ofTest(cfg) : Repository.of(cfg));
  }

  protected IngestServer(Config cfg, Repository repo) {
    this.cfg = cfg;
    this.repo = repo;
    direct = repo.getOrCreateOperator(DirectDataOperator.class);
    if (log.isDebugEnabled()) {
      repo.getAllEntities()
          .forEach(
              e -> e.getAllAttributes(true).forEach(a -> log.debug("Configured attribute {}", a)));
    }
    if (repo.isEmpty()) {
      throw new IllegalArgumentException("No valid entities found in provided config!");
    }
    this.ignoreErrors =
        cfg.hasPath(Constants.CFG_IGNORE_ERRORS) && cfg.getBoolean(Constants.CFG_IGNORE_ERRORS);
    this.transactionContext =
        direct.getRepository().findEntity(ConfigConstants.TRANSACTION_ENTITY).isPresent()
            ? new TransactionContext(direct)
            : null;
    executor = createExecutor(cfg);
  }

  static boolean ingestRequest(
      DirectDataOperator direct,
      StreamElement ingest,
      String uuid,
      Consumer responseConsumer) {

    AttributeDescriptor attributeDesc = ingest.getAttributeDescriptor();

    OnlineAttributeWriter writer = getWriterForAttributeInTransform(direct, attributeDesc);

    if (writer == null) {
      log.warn("Missing writer for request {}", ingest);
      responseConsumer.accept(
          status(uuid, 503, "No writer for attribute " + attributeDesc.getName()));
      return false;
    }

    if (ingest.isDelete()) {
      if (ingest.isDeleteWildcard()) {
        Metrics.DELETE_WILDCARD_REQUESTS.increment();
      } else {
        Metrics.DELETE_REQUESTS.increment();
      }
    } else {
      Metrics.UPDATE_REQUESTS.increment();
    }

    Metrics.COMMIT_LOG_APPEND.increment();
    // write the ingest into the commit log and confirm to the client
    log.debug("Writing {} to commit log {}", ingest, writer.getUri());
    writer.write(
        ingest,
        (s, exc) -> {
          if (s) {
            responseConsumer.accept(ok(uuid));
          } else {
            log.warn("Failed to write {}", ingest, exc);
            responseConsumer.accept(status(uuid, 500, exc.toString()));
          }
        });
    return true;
  }

  private static OnlineAttributeWriter getWriterForAttributeInTransform(
      DirectDataOperator direct, AttributeDescriptor attributeDesc) {

    return direct
        .getWriter(attributeDesc)
        .orElseThrow(
            () ->
                new IllegalStateException(
                    "Writer for attribute " + attributeDesc.getName() + " not found"));
  }

  static Rpc.Status notFound(String uuid, String what) {
    return Rpc.Status.newBuilder().setUuid(uuid).setStatus(404).setStatusMessage(what).build();
  }

  static Rpc.Status ok(String uuid) {
    return Rpc.Status.newBuilder().setStatus(200).setUuid(uuid).build();
  }

  static Rpc.Status status(String uuid, int status, String message) {
    return Rpc.Status.newBuilder()
        .setUuid(uuid)
        .setStatus(status)
        .setStatusMessage(message)
        .build();
  }

  protected Executor createExecutor(Config cfg) {
    int numThreads =
        cfg.hasPath(CFG_NUM_THREADS) ? cfg.getInt(CFG_NUM_THREADS) : DEFAULT_NUM_THREADS;
    return new ThreadPoolExecutor(
        numThreads, numThreads, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
  }

  /** Run the server. */
  protected void run() {
    final int port =
        cfg.hasPath(Constants.CFG_PORT) ? cfg.getInt(Constants.CFG_PORT) : Constants.DEFAULT_PORT;

    io.grpc.Server server = createServer(port);

    Runtime.getRuntime()
        .addShutdownHook(
            new Thread(
                () -> {
                  log.info("Gracefully shutting server down.");
                  server.shutdown();
                }));

    try {
      server.start();
      Optional.ofNullable(transactionContext).ifPresent(TransactionContext::run);
      log.info("Successfully started server 0.0.0.0:{}", server.getPort());
      Metrics.LIVENESS.increment(1.0);
      server.awaitTermination();
      log.info("Server shutdown.");
    } catch (Exception ex) {
      log.error("Failed to start the server", ex);
    } finally {
      Optional.ofNullable(transactionContext).ifPresent(TransactionContext::close);
    }
    Metrics.LIVENESS.reset();
  }

  private Server createServer(int port) {
    return createServer(port, log.isDebugEnabled());
  }

  protected Server createServer(int port, boolean debug) {
    ServerBuilder builder = ServerBuilder.forPort(port).executor(executor);
    getServices().forEach(builder::addService);
    if (debug) {
      builder = builder.intercept(new IngestServerInterceptor());
    }
    return builder.build();
  }

  protected Iterable getServices() {
    return Arrays.asList(
        new IngestService(repo, direct, transactionContext, scheduler),
        new RetrieveService(repo, direct, transactionContext));
  }

  @VisibleForTesting
  void runReplications() {
    final ReplicationController replicationController = ReplicationController.of(repo);
    replicationController
        .runReplicationThreads()
        .whenComplete(
            (success, error) -> {
              if (error != null) {
                Utils.die(error.getMessage(), error);
              }
            });
  }

  private static class IngestServerInterceptor implements io.grpc.ServerInterceptor {

    @Override
    public  Listener interceptCall(
        ServerCall serverCall,
        Metadata metadata,
        ServerCallHandler serverCallHandler) {

      log.debug(
          "Received call {} with attributes {} and metadata {}",
          serverCall.getMethodDescriptor(),
          serverCall.getAttributes(),
          metadata);
      return serverCallHandler.startCall(serverCall, metadata);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy