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 == nil { 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, URL: &url.URL{}} *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 = nil } // 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"` }