190 lines
4.4 KiB
Go
190 lines
4.4 KiB
Go
package raft
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/url"
|
|
)
|
|
|
|
// Config represents the configuration for the log.
|
|
type Config struct {
|
|
// Cluster identifier. Used to prevent separate clusters from
|
|
// accidentally communicating with one another.
|
|
ClusterID uint64
|
|
|
|
// Index is the last log index when the configuration was updated.
|
|
Index uint64
|
|
|
|
// MaxNodeID is the largest node identifier generated for this config.
|
|
MaxNodeID uint64
|
|
|
|
// List of nodes in the cluster.
|
|
Nodes []*ConfigNode
|
|
}
|
|
|
|
// NodeByID returns a node by identifier.
|
|
func (c *Config) NodeByID(id uint64) *ConfigNode {
|
|
for _, n := range c.Nodes {
|
|
if n.ID == id {
|
|
return n
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NodeByURL returns a node by URL.
|
|
func (c *Config) NodeByURL(u url.URL) *ConfigNode {
|
|
for _, n := range c.Nodes {
|
|
if n.URL.String() == u.String() {
|
|
return n
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddNode adds a new node to the config.
|
|
func (c *Config) AddNode(id uint64, u url.URL) error {
|
|
// Validate that the id is non-zero and the url exists.
|
|
if id == 0 {
|
|
return ErrInvalidNodeID
|
|
} else if u.Host == "" {
|
|
return ErrNodeURLRequired
|
|
}
|
|
|
|
// Validate that no other nodes in the config have the same id or URL.
|
|
for _, n := range c.Nodes {
|
|
if n.ID == id {
|
|
return ErrDuplicateNodeID
|
|
} else if n.URL.String() == u.String() {
|
|
return ErrDuplicateNodeURL
|
|
}
|
|
}
|
|
|
|
// Add the node to the config.
|
|
c.Nodes = append(c.Nodes, &ConfigNode{ID: id, URL: u})
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveNode removes a node by id.
|
|
// Returns ErrNodeNotFound if the node does not exist.
|
|
func (c *Config) RemoveNode(id uint64) error {
|
|
for i, node := range c.Nodes {
|
|
if node.ID == id {
|
|
copy(c.Nodes[i:], c.Nodes[i+1:])
|
|
c.Nodes[len(c.Nodes)-1] = nil
|
|
c.Nodes = c.Nodes[:len(c.Nodes)-1]
|
|
return nil
|
|
}
|
|
}
|
|
return ErrNodeNotFound
|
|
}
|
|
|
|
// Clone returns a deep copy of the configuration.
|
|
func (c *Config) Clone() *Config {
|
|
other := &Config{
|
|
ClusterID: c.ClusterID,
|
|
Index: c.Index,
|
|
MaxNodeID: c.MaxNodeID,
|
|
}
|
|
other.Nodes = make([]*ConfigNode, len(c.Nodes))
|
|
for i, n := range c.Nodes {
|
|
other.Nodes[i] = n.clone()
|
|
}
|
|
return other
|
|
}
|
|
|
|
// ConfigNode represents a single machine in the raft configuration.
|
|
type ConfigNode struct {
|
|
ID uint64
|
|
URL url.URL
|
|
}
|
|
|
|
// clone returns a deep copy of the node.
|
|
func (n *ConfigNode) clone() *ConfigNode {
|
|
other := &ConfigNode{ID: n.ID}
|
|
other.URL = n.URL
|
|
return other
|
|
}
|
|
|
|
// ConfigEncoder encodes a config to a writer.
|
|
type ConfigEncoder struct {
|
|
w io.Writer
|
|
}
|
|
|
|
// NewConfigEncoder returns a new instance of ConfigEncoder attached to a writer.
|
|
func NewConfigEncoder(w io.Writer) *ConfigEncoder {
|
|
return &ConfigEncoder{w}
|
|
}
|
|
|
|
// Encode marshals the configuration to the encoder's writer.
|
|
func (enc *ConfigEncoder) Encode(c *Config) error {
|
|
// Copy properties to intermediate type.
|
|
var o configJSON
|
|
o.ClusterID = c.ClusterID
|
|
o.Index = c.Index
|
|
o.MaxNodeID = c.MaxNodeID
|
|
for _, n := range c.Nodes {
|
|
o.Nodes = append(o.Nodes, &configNodeJSON{ID: n.ID, URL: n.URL.String()})
|
|
}
|
|
|
|
// Encode intermediate type as JSON.
|
|
return json.NewEncoder(enc.w).Encode(&o)
|
|
}
|
|
|
|
// ConfigDecoder decodes a config from a reader.
|
|
type ConfigDecoder struct {
|
|
r io.Reader
|
|
}
|
|
|
|
// NewConfigDecoder returns a new instance of ConfigDecoder attached to a reader.
|
|
func NewConfigDecoder(r io.Reader) *ConfigDecoder {
|
|
return &ConfigDecoder{r}
|
|
}
|
|
|
|
// Decode marshals the configuration to the decoder's reader.
|
|
func (dec *ConfigDecoder) Decode(c *Config) error {
|
|
// Decode into intermediate type.
|
|
var o configJSON
|
|
if err := json.NewDecoder(dec.r).Decode(&o); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy properties to config.
|
|
c.ClusterID = o.ClusterID
|
|
c.Index = o.Index
|
|
c.MaxNodeID = o.MaxNodeID
|
|
|
|
// Validate and append nodes.
|
|
for _, n := range o.Nodes {
|
|
// Parse node URL.
|
|
u, err := url.Parse(n.URL)
|
|
if err != nil {
|
|
return err
|
|
} else if n.URL == "" {
|
|
u = &url.URL{}
|
|
}
|
|
|
|
// Append node to config.
|
|
if err := c.AddNode(n.ID, *u); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// configJSON represents an intermediate struct used for JSON encoding.
|
|
type configJSON struct {
|
|
ClusterID uint64 `json:"clusterID,omitempty"`
|
|
Index uint64 `json:"index,omitempty"`
|
|
MaxNodeID uint64 `json:"maxNodeID,omitempty"`
|
|
Nodes []*configNodeJSON `json:"nodes,omitempty"`
|
|
}
|
|
|
|
// configNodeJSON represents the JSON serialized form of the ConfigNode type.
|
|
type configNodeJSON struct {
|
|
ID uint64 `json:"id"`
|
|
URL string `json:"url,omitempty"`
|
|
}
|