Beispiel #1
0
  /**
   * Verify that the protocols are compatible, switch to a different protocol version, if we need
   * to.
   */
  private Protocol checkProtocol(Protocol protocol) throws IOException, ProtocolException {

    ClientVersion clientVersion =
        protocol.read(namedChannel.getChannel(), Protocol.ClientVersion.class);
    clientId = clientVersion.getNodeId();
    FeederManager.Lease lease = feederManager.leases.get(clientId);
    if (lease != null) {
      dbBackup = lease.terminate();
    }
    feederManager.feeders.put(clientId, this);
    if (clientVersion.getVersion() != protocol.getVersion()) {
      String message =
          "Client requested protocol version: "
              + clientVersion.getVersion()
              + " but the server version is "
              + protocol.getVersion();

      /*
       * Simply log the difference on the server, it's up to the client
       * to reject the protocol version, if it can't accommodate it.
       */
      LoggerUtils.warning(logger, feederManager.getEnvImpl(), message);
    }
    protocol.write(protocol.new ServerVersion(), namedChannel);

    /* In future we may switch protocol versions to accommodate the client.
     * For now, simply return the one and only version.
     */
    return protocol;
  }
Beispiel #2
0
  /** Sets up the channel to facilitate efficient transfer of large log files. */
  private SocketChannel configureChannel() throws IOException {

    namedChannel.getChannel().configureBlocking(true);
    LoggerUtils.fine(
        logger,
        feederManager.getEnvImpl(),
        "Log File Feeder accepted connection from " + namedChannel);
    namedChannel.getChannel().socket().setSoTimeout(SOCKET_TIMEOUT_MS);

    /*
     * Enable Nagle's algorithm since throughput is important for the large
     * files we will be transferring.
     */
    namedChannel.getChannel().socket().setTcpNoDelay(false);
    return namedChannel.getChannel();
  }
Beispiel #3
0
  /**
   * Processes the request for the list of files that constitute a valid backup. If a leased
   * DbBackup instance is available, it uses it, otherwise it creates a new instance and uses it
   * instead.
   */
  private void sendFileList(Protocol protocol)
      throws IOException, ProtocolException, DatabaseException {

    /* Wait for the request message. */
    protocol.read(namedChannel.getChannel(), Protocol.FileListReq.class);

    if (dbBackup == null) {
      dbBackup = new DbBackup(feederManager.getEnvImpl());
      dbBackup.startBackup();
    } else {
      feederManager.leaseRenewalCount++;
    }

    /*
     * Remove the subdirectory header of the log files, because the nodes
     * that need to copy those log files may not configure the spreading
     * log files into sub directories feature.
     */
    String[] files = dbBackup.getLogFilesInBackupSet();
    for (int i = 0; i < files.length; i++) {
      if (files[i].contains(File.separator)) {
        files[i] = files[i].substring(files[i].indexOf(File.separator) + 1, files[i].length());
      }
    }

    protocol.write(protocol.new FileListResp(files), namedChannel);
  }
Beispiel #4
0
  /** The main driver loop that enforces the protocol message sequence and implements it. */
  @Override
  public void run() {
    /* The initial protocol */
    Protocol protocol =
        new Protocol(feederManager.nameIdPair, Protocol.VERSION, feederManager.getEnvImpl());
    try {
      configureChannel();
      protocol = checkProtocol(protocol);
      checkFeeder(protocol);
      sendFileList(protocol);
      sendRequestedFiles(protocol);

      /* Done, cleanup */
      dbBackup.endBackup();
      dbBackup = null;
    } catch (ClosedByInterruptException e) {
      LoggerUtils.fine(
          logger,
          feederManager.getEnvImpl(),
          "Ignoring ClosedByInterruptException normal shutdown");
    } catch (IOException e) {
      LoggerUtils.warning(logger, feederManager.getEnvImpl(), " IO Exception: " + e.getMessage());
    } catch (ProtocolException e) {
      LoggerUtils.severe(
          logger, feederManager.getEnvImpl(), " Protocol Exception: " + e.getMessage());
    } catch (Exception e) {
      throw new EnvironmentFailureException(
          feederManager.getEnvImpl(), EnvironmentFailureReason.UNCAUGHT_EXCEPTION, e);
    } finally {
      try {
        namedChannel.getChannel().close();
      } catch (IOException e) {
        LoggerUtils.warning(
            logger,
            feederManager.getEnvImpl(),
            "Log File feeder io exception on " + "channel close: " + e.getMessage());
      }
      shutdown();

      if (dbBackup != null) {
        if (feederManager.shutdown.get()) {
          dbBackup.endBackup();
        } else {

          /*
           * Establish lease so client can resume within the lease
           * period.
           */
          feederManager.new Lease(clientId, feederManager.leaseDuration, dbBackup);
          LoggerUtils.info(
              logger, feederManager.getEnvImpl(), "Lease created for node: " + clientId);
        }
      }
      LoggerUtils.info(
          logger,
          feederManager.getEnvImpl(),
          "Log file feeder for client: " + clientId + " exited");
    }
  }
Beispiel #5
0
  /**
   * Sends over the contents of the file and computes the SHA-1 hash. Note that the method does not
   * rely on EOF detection, but rather on the promised file size, since the final log file might be
   * growing while the transfer is in progress. The client uses the length sent in the FileResp
   * message to maintain its position in the network stream. It expects to see a StatResp once it
   * has read the agreed upon number of bytes.
   *
   * <p>Since JE log files are append only, there is no danger that we will send over any
   * uninitialized file blocks.
   *
   * @param file the log file to be sent.
   * @param the number of bytes to send
   * @return the digest associated with the file that was sent
   * @throws IOException
   */
  private byte[] sendFileContents(File file, long length) throws IOException {

    final LogVerifier verifier = new LogVerifier(feederManager.getEnvImpl(), file.getName(), -1L);
    final FileInputStream fileStream = new FileInputStream(file);

    try {
      final FileChannel fileChannel = fileStream.getChannel();
      messageDigest.reset();
      final ByteBuffer buffer = ByteBuffer.allocateDirect(TRANSFER_BYTES);
      final byte[] array = (buffer.hasArray()) ? buffer.array() : new byte[TRANSFER_BYTES];
      int transmitBytes = 0;

      while (true) {
        buffer.clear();
        if (fileChannel.read(buffer) < 0) {
          verifier.verifyAtEof();
          break;
        }

        buffer.flip();
        final int lim = buffer.limit();
        final int off;
        if (buffer.hasArray()) {
          off = buffer.arrayOffset();
        } else {
          off = 0;
          buffer.get(array, 0, lim);
          buffer.rewind();
        }
        verifier.verify(array, off, lim);
        messageDigest.update(array, off, lim);
        transmitBytes += namedChannel.getChannel().write(buffer);
      }

      if (transmitBytes != length) {
        String msg =
            "File length:"
                + length
                + " does not match the "
                + "number of bytes that were transmitted:"
                + transmitBytes;

        throw new IllegalStateException(msg);
      }

      LoggerUtils.info(
          logger,
          feederManager.getEnvImpl(),
          "Sent file: " + file + " Length: " + length + " bytes");
    } finally {
      fileStream.close();
    }
    return messageDigest.digest();
  }
Beispiel #6
0
  /**
   * Implements the message exchange used to determine whether this feeder is suitable for use the
   * client's backup needs. The feeder may be unsuitable if it's already busy, or it's not current
   * enough to service the client's needs.
   */
  private void checkFeeder(Protocol protocol) throws IOException, DatabaseException {

    protocol.read(namedChannel.getChannel(), FeederInfoReq.class);
    int feeders = feederManager.getActiveFeederCount() - 1 /* Exclude this one */;
    VLSN rangeFirst = VLSN.NULL_VLSN;
    VLSN rangeLast = VLSN.NULL_VLSN;
    if (feederManager.getEnvImpl() instanceof RepImpl) {
      /* Include replication stream feeders as a load component. */
      RepImpl repImpl = (RepImpl) feederManager.getEnvImpl();
      feeders += repImpl.getRepNode().feederManager().activeReplicaCount();
      VLSNRange range = repImpl.getVLSNIndex().getRange();
      rangeFirst = range.getFirst();
      rangeLast = range.getLast();
    }
    protocol.write(protocol.new FeederInfoResp(feeders, rangeFirst, rangeLast), namedChannel);
  }
Beispiel #7
0
  /**
   * Send files in response to request messages. The request sequence looks like the following:
   *
   * <p>[FileReq | StatReq]+ Done
   *
   * <p>The response sequence to a FileReq looks like:
   *
   * <p>FileStart <file byte stream> FileEnd
   *
   * <p>and that for a StatReq, is simply a StatResp
   */
  private void sendRequestedFiles(Protocol protocol)
      throws IOException, ProtocolException, DatabaseException {

    try {
      while (true) {
        FileReq fileReq = protocol.read(namedChannel.getChannel(), FileReq.class);
        final String fileName = fileReq.getFileName();

        /*
         * Calculate the full path for a specified log file name,
         * especially when this Feeder is configured to run with sub
         * directories.
         */
        FileManager fMgr = feederManager.getEnvImpl().getFileManager();
        File file = new File(fMgr.getFullFileName(fileName));

        if (!file.exists()) {
          throw EnvironmentFailureException.unexpectedState("Log file not found: " + fileName);
        }
        /* Freeze the length and last modified date. */
        final long length = file.length();
        final long lastModified = file.lastModified();
        byte digest[] = null;
        FileInfoResp resp = null;
        Protocol.FileInfoResp cachedResp = feederManager.statResponses.get(fileName);
        byte cachedDigest[] =
            ((cachedResp != null)
                    && (cachedResp.getFileLength() == length)
                    && (cachedResp.getLastModifiedTime() == lastModified))
                ? cachedResp.getDigestSHA1()
                : null;

        if (fileReq instanceof FileInfoReq) {
          if (cachedDigest != null) {
            digest = cachedDigest;
          } else if (((FileInfoReq) fileReq).getNeedSHA1()) {
            digest = getSHA1Digest(file, length).digest();
          } else {
            // Digest not requested
            digest = new byte[0];
          }
          resp = protocol.new FileInfoResp(fileName, length, lastModified, digest);
        } else {
          protocol.write(protocol.new FileStart(fileName, length, lastModified), namedChannel);
          digest = sendFileContents(file, length);
          if ((cachedDigest != null) && !Arrays.equals(cachedDigest, digest)) {
            throw EnvironmentFailureException.unexpectedState(
                "Inconsistent cached and computed digests");
          }
          resp = protocol.new FileEnd(fileName, length, lastModified, digest);
        }
        /* Cache for subsequent requests, if it was computed. */
        if (digest.length > 0) {
          feederManager.statResponses.put(fileName, resp);
        }
        protocol.write(resp, namedChannel);
      }
    } catch (ProtocolException pe) {
      if (pe.getUnexpectedMessage() instanceof Protocol.Done) {
        return;
      }
      throw pe;
    }
  }