diff --git a/peersjson.go b/peersjson.go index c55fdbb43..38ca2a8b8 100644 --- a/peersjson.go +++ b/peersjson.go @@ -44,3 +44,55 @@ func ReadPeersJSON(path string) (Configuration, error) { } return configuration, nil } + +// configEntry is used when decoding a new-style peers.json. +type configEntry struct { + // ID is the ID of the server (a UUID, usually). + ID ServerID `json:"id"` + + // Address is the host:port of the server. + Address ServerAddress `json:"address"` + + // NonVoter controls the suffrage. We choose this sense so people + // can leave this out and get a Voter by default. + NonVoter bool `json:"non_voter"` +} + +// ReadConfigJSON reads a new-style peers.json and returns a configuration +// structure. This can be used to perform manual recovery when running protocol +// versions that use server IDs. +func ReadConfigJSON(path string) (Configuration, error) { + // Read in the file. + buf, err := ioutil.ReadFile(path) + if err != nil { + return Configuration{}, err + } + + // Parse it as JSON. + var peers []configEntry + dec := json.NewDecoder(bytes.NewReader(buf)) + if err := dec.Decode(&peers); err != nil { + return Configuration{}, err + } + + // Map it into the new-style configuration structure. + var configuration Configuration + for _, peer := range peers { + suffrage := Voter + if peer.NonVoter { + suffrage = Nonvoter + } + server := Server{ + Suffrage: suffrage, + ID: peer.ID, + Address: peer.Address, + } + configuration.Servers = append(configuration.Servers, server) + } + + // We should only ingest valid configurations. + if err := checkConfiguration(configuration); err != nil { + return Configuration{}, err + } + return configuration, nil +} diff --git a/peersjson_test.go b/peersjson_test.go index d177251af..be751e1c8 100644 --- a/peersjson_test.go +++ b/peersjson_test.go @@ -27,14 +27,18 @@ func TestPeersJSON_BadConfiguration(t *testing.T) { } } -func Test_PeersJSON(t *testing.T) { +func TestPeersJSON_ReadPeersJSON(t *testing.T) { base, err := ioutil.TempDir("", "") if err != nil { t.Fatalf("err: %v", err) } defer os.RemoveAll(base) - content := []byte("[\"127.0.0.1:123\", \"127.0.0.2:123\", \"127.0.0.3:123\"]") + content := []byte(` +["127.0.0.1:123", + "127.0.0.2:123", + "127.0.0.3:123"] +`) peers := filepath.Join(base, "peers.json") if err := ioutil.WriteFile(peers, content, 0666); err != nil { t.Fatalf("err: %v", err) @@ -68,3 +72,62 @@ func Test_PeersJSON(t *testing.T) { t.Fatalf("bad configuration: %+v != %+v", configuration, expected) } } + +func TestPeersJSON_ReadConfigJSON(t *testing.T) { + base, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("err: %v", err) + } + defer os.RemoveAll(base) + + content := []byte(` +[ + { + "id": "adf4238a-882b-9ddc-4a9d-5b6758e4159e", + "address": "127.0.0.1:123", + "non_voter": false + }, + { + "id": "8b6dda82-3103-11e7-93ae-92361f002671", + "address": "127.0.0.2:123" + }, + { + "id": "97e17742-3103-11e7-93ae-92361f002671", + "address": "127.0.0.3:123", + "non_voter": true + } +] +`) + peers := filepath.Join(base, "peers.json") + if err := ioutil.WriteFile(peers, content, 0666); err != nil { + t.Fatalf("err: %v", err) + } + + configuration, err := ReadConfigJSON(peers) + if err != nil { + t.Fatalf("err: %v", err) + } + + expected := Configuration{ + Servers: []Server{ + Server{ + Suffrage: Voter, + ID: ServerID("adf4238a-882b-9ddc-4a9d-5b6758e4159e"), + Address: ServerAddress("127.0.0.1:123"), + }, + Server{ + Suffrage: Voter, + ID: ServerID("8b6dda82-3103-11e7-93ae-92361f002671"), + Address: ServerAddress("127.0.0.2:123"), + }, + Server{ + Suffrage: Nonvoter, + ID: ServerID("97e17742-3103-11e7-93ae-92361f002671"), + Address: ServerAddress("127.0.0.3:123"), + }, + }, + } + if !reflect.DeepEqual(configuration, expected) { + t.Fatalf("bad configuration: %+v != %+v", configuration, expected) + } +}