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

net.data.technology.jraft.extensions.FileBasedServerStateManager Maven / Gradle / Ivy

Go to download

extension project for Jraft (Java implementation of Raft Consensus), this module contains may default implementations for interfaces that required by Jraft

There is a newer version: 1.0.1
Show newest version
/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  The ASF licenses 
 * this file to you 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 net.data.technology.jraft.extensions;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import net.data.technology.jraft.ClusterConfiguration;
import net.data.technology.jraft.SequentialLogStore;
import net.data.technology.jraft.ServerState;
import net.data.technology.jraft.ServerStateManager;

public class FileBasedServerStateManager implements ServerStateManager {

    private static final String STATE_FILE = "server.state";
    private static final String CONFIG_FILE = "config.properties";
    private static final String CLUSTER_CONFIG_FILE = "cluster.json";

    private RandomAccessFile serverStateFile;
    private FileBasedSequentialLogStore logStore;
    private Logger logger;
    private Path container;
    private int serverId;

    public FileBasedServerStateManager(String dataDirectory){
        this.logStore = new FileBasedSequentialLogStore(dataDirectory);
        this.container = Paths.get(dataDirectory);
        this.logger = LogManager.getLogger(getClass());
        try{
            Properties props = new Properties();
            FileInputStream configInput = new FileInputStream(this.container.resolve(CONFIG_FILE).toString());
            props.load(configInput);
            String serverIdValue = props.getProperty("server.id");
            this.serverId = serverIdValue == null || serverIdValue.length() == 0 ? -1 : Integer.parseInt(serverIdValue.trim());
            configInput.close();
            this.serverStateFile = new RandomAccessFile(this.container.resolve(STATE_FILE).toString(), "rw");
            this.serverStateFile.seek(0);
        }catch(IOException exception){
            this.logger.error("failed to create/open server state file", exception);
            throw new IllegalArgumentException("cannot create/open the state file", exception);
        }
    }

    @Override
    public ClusterConfiguration loadClusterConfiguration(){
        Gson gson = new GsonBuilder().create();
        FileInputStream stream = null;

        try{
            stream = new FileInputStream(this.container.resolve(CLUSTER_CONFIG_FILE).toString());
            ClusterConfiguration config = gson.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), ClusterConfiguration.class);
            return config;
        }catch(IOException error){
            this.logger.error("failed to read cluster configuration", error);
            throw new RuntimeException("failed to read in cluster config", error);
        }finally{
            if(stream != null){
                try{
                    stream.close();
                }catch(Exception e){
                    //ignore the error
                }
            }
        }
    }

    public void saveClusterConfiguration(ClusterConfiguration configuration){
        Gson gson = new GsonBuilder().create();
        String configData = gson.toJson(configuration);
        try {
            Files.deleteIfExists(this.container.resolve(CLUSTER_CONFIG_FILE));
            FileOutputStream output = new FileOutputStream(this.container.resolve(CLUSTER_CONFIG_FILE).toString());
            output.write(configData.getBytes(StandardCharsets.UTF_8));
            output.flush();
            output.close();
        } catch (IOException error) {
            this.logger.error("failed to save cluster config to file", error);
        }
    }

    public int getServerId(){
        return this.serverId;
    }

    @Override
    public synchronized void persistState(ServerState serverState) {
        try{
            ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES * 2 + Integer.BYTES);
            buffer.putLong(serverState.getTerm());
            buffer.putLong(serverState.getCommitIndex());
            buffer.putInt(serverState.getVotedFor());
            this.serverStateFile.write(buffer.array());
            this.serverStateFile.seek(0);
        }catch(IOException ioError){
            this.logger.error("failed to write to the server state file", ioError);
            throw new RuntimeException("fatal I/O error while writing to the state file", ioError);
        }
    }

    @Override
    public synchronized ServerState readState() {
        try{
            if(this.serverStateFile.length() == 0){
                return null;
            }

            byte[] stateData = new byte[Long.BYTES * 2 + Integer.BYTES];
            this.read(stateData);
            this.serverStateFile.seek(0);
            ByteBuffer buffer = ByteBuffer.wrap(stateData);
            ServerState state = new ServerState();
            state.setTerm(buffer.getLong());
            state.setCommitIndex(buffer.getLong());
            state.setVotedFor(buffer.getInt());
            return state;
        }catch(IOException ioError){
            this.logger.error("failed to read from the server state file", ioError);
            throw new RuntimeException("fatal I/O error while reading from state file", ioError);
        }
    }

    @Override
    public SequentialLogStore loadLogStore() {
        return this.logStore;
    }

    public void close(){
        try{
            this.serverStateFile.close();
            this.logStore.close();
        }catch(IOException exception){
            this.logger.info("failed to shutdown the server state manager due to io error", exception);
        }
    }

    private void read(byte[] buffer){
        try{
            int offset = 0;
            int bytesRead = 0;
            while(offset < buffer.length && (bytesRead = this.serverStateFile.read(buffer, offset, buffer.length - offset)) != -1){
                offset += bytesRead;
            }

            if(offset < buffer.length){
                this.logger.error(String.format("only %d bytes are read while %d bytes are desired, bad file", offset, buffer.length));
                throw new RuntimeException("bad file, insufficient file data for reading");
            }
        }catch(IOException exception){
            this.logger.error("failed to read and fill the buffer", exception);
            throw new RuntimeException(exception.getMessage(), exception);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy