1package raft
2
3import (
4	"bytes"
5	"encoding/json"
6	"io/ioutil"
7)
8
9// ReadPeersJSON consumes a legacy peers.json file in the format of the old JSON
10// peer store and creates a new-style configuration structure. This can be used
11// to migrate this data or perform manual recovery when running protocol versions
12// that can interoperate with older, unversioned Raft servers. This should not be
13// used once server IDs are in use, because the old peers.json file didn't have
14// support for these, nor non-voter suffrage types.
15func ReadPeersJSON(path string) (Configuration, error) {
16	// Read in the file.
17	buf, err := ioutil.ReadFile(path)
18	if err != nil {
19		return Configuration{}, err
20	}
21
22	// Parse it as JSON.
23	var peers []string
24	dec := json.NewDecoder(bytes.NewReader(buf))
25	if err := dec.Decode(&peers); err != nil {
26		return Configuration{}, err
27	}
28
29	// Map it into the new-style configuration structure. We can only specify
30	// voter roles here, and the ID has to be the same as the address.
31	var configuration Configuration
32	for _, peer := range peers {
33		server := Server{
34			Suffrage: Voter,
35			ID:       ServerID(peer),
36			Address:  ServerAddress(peer),
37		}
38		configuration.Servers = append(configuration.Servers, server)
39	}
40
41	// We should only ingest valid configurations.
42	if err := checkConfiguration(configuration); err != nil {
43		return Configuration{}, err
44	}
45	return configuration, nil
46}
47
48// configEntry is used when decoding a new-style peers.json.
49type configEntry struct {
50	// ID is the ID of the server (a UUID, usually).
51	ID ServerID `json:"id"`
52
53	// Address is the host:port of the server.
54	Address ServerAddress `json:"address"`
55
56	// NonVoter controls the suffrage. We choose this sense so people
57	// can leave this out and get a Voter by default.
58	NonVoter bool `json:"non_voter"`
59}
60
61// ReadConfigJSON reads a new-style peers.json and returns a configuration
62// structure. This can be used to perform manual recovery when running protocol
63// versions that use server IDs.
64func ReadConfigJSON(path string) (Configuration, error) {
65	// Read in the file.
66	buf, err := ioutil.ReadFile(path)
67	if err != nil {
68		return Configuration{}, err
69	}
70
71	// Parse it as JSON.
72	var peers []configEntry
73	dec := json.NewDecoder(bytes.NewReader(buf))
74	if err := dec.Decode(&peers); err != nil {
75		return Configuration{}, err
76	}
77
78	// Map it into the new-style configuration structure.
79	var configuration Configuration
80	for _, peer := range peers {
81		suffrage := Voter
82		if peer.NonVoter {
83			suffrage = Nonvoter
84		}
85		server := Server{
86			Suffrage: suffrage,
87			ID:       peer.ID,
88			Address:  peer.Address,
89		}
90		configuration.Servers = append(configuration.Servers, server)
91	}
92
93	// We should only ingest valid configurations.
94	if err := checkConfiguration(configuration); err != nil {
95		return Configuration{}, err
96	}
97	return configuration, nil
98}
99