simplify etcd packages

This commit is contained in:
Travis Nielsen
2016-10-25 11:00:34 -07:00
parent 0c3be216db
commit 97b6a1d70d
12 changed files with 146 additions and 183 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/quantum/castle/pkg/clusterd/inventory"
"github.com/quantum/castle/pkg/etcdmgr/manager"
"github.com/quantum/castle/pkg/etcdmgr/bootstrap"
"github.com/quantum/castle/pkg/util"
"github.com/quantum/castle/pkg/util/exec"
"github.com/quantum/castle/pkg/util/proc"
@@ -25,7 +25,7 @@ func StartJoinCluster(services []*ClusterService, procMan *proc.ProcManager, con
// use the discovery URL to query the etcdmgr what the etcd client endpoints should be
var err error
etcdClients, err = manager.GetEtcdClients(configDir, discoveryURL, privateIPv4, nodeID)
etcdClients, err = bootstrap.GetEtcdClients(configDir, discoveryURL, privateIPv4, nodeID)
if err != nil {
return nil, err
}
@@ -8,7 +8,6 @@ import (
etcd "github.com/coreos/etcd/client"
"github.com/quantum/castle/pkg/clusterd"
"github.com/quantum/castle/pkg/etcdmgr/bootstrap"
"github.com/quantum/castle/pkg/etcdmgr/manager"
"github.com/quantum/castle/pkg/util"
ctx "golang.org/x/net/context"
)
@@ -79,7 +78,7 @@ func (e *etcdMgrAgent) CreateLocalService(context *clusterd.Context, desiredKey
if !etcdmgrApplied {
log.Println("adding the current node to the etcd cluster...")
targetEndpoint := getPeerEndpointFromIP(ipAddr)
err = manager.AddMember(e.context, targetEndpoint)
err = AddMember(e.context, targetEndpoint)
if err != nil {
return fmt.Errorf("error in adding a member to the cluster")
}
@@ -120,5 +119,5 @@ func getEtcdMgrAppliedKey(nodeID string) string {
}
func getPeerEndpointFromIP(ip string) string {
return "http://" + ip + ":" + bootstrap.DefaultPeerPort
return fmt.Sprintf("http://%s:%d", ip, bootstrap.DefaultPeerPort)
}
+54
View File
@@ -0,0 +1,54 @@
package bootstrap
import (
"errors"
"fmt"
"log"
"os"
)
// GetEtcdClients bootstraps an embedded etcd instance and returns a list of
// current etcd cluster's client URLs. (entrypoint, when it's used as a library)
func GetEtcdClients(configDir, token, ipAddr, nodeID string) ([]string, error) {
// GODEBUG setting forcing use of Go's resolver
os.Setenv("GODEBUG", "netdns=go+1")
full, err, currentNodes := isQuorumFull(token)
//currentNodes, err := GetCurrentNodesFromDiscovery(token)
if err != nil {
return []string{}, errors.New("error querying discovery service")
}
log.Println("current etcd cluster nodes: ", currentNodes)
localURL := fmt.Sprintf("http://%s:%d", ipAddr, DefaultClientPort)
// Is it a restart scenario?
restart := false
log.Println("current localURL: ", localURL)
for _, node := range currentNodes {
if node == localURL {
log.Println("restart scenario detected.")
restart = true
}
}
if full && !restart {
log.Println("quorum is already formed, returning current cluster members: ", currentNodes)
return currentNodes, nil
}
log.Println("quorum is not complete, creating a new embedded etcd member...")
conf, err := generateConfig(configDir, ipAddr, nodeID)
if err != nil {
return []string{}, err
}
log.Println("conf:", conf)
factory := EmbeddedEtcdFactory{}
ee, err := factory.NewEmbeddedEtcd(token, conf, true)
if err != nil {
return nil, err
}
return ee.Server.Cluster().ClientURLs(), nil
}
+8 -7
View File
@@ -1,6 +1,7 @@
package bootstrap
import (
"fmt"
"log"
"net/url"
"path"
@@ -11,9 +12,9 @@ import (
const (
// DefaultClientPort is the default port for listening to client traffic
DefaultClientPort = "53379"
DefaultClientPort = 53379
// DefaultPeerPort is the default port for listening to peer traffic
DefaultPeerPort = "53380"
DefaultPeerPort = 53380
// DefaultClientTimeout is the default timeout for etcd client
DefaultClientTimeout = 5 * time.Second
)
@@ -44,8 +45,8 @@ func CopyConfig(conf *Config) Config {
return newConf
}
// GenerateConfig automatically generates a config for embedded etcd
func GenerateConfig(configDir, ipAddr, nodeID string) (*Config, error) {
// generateConfig automatically generates a config for embedded etcd
func generateConfig(configDir, ipAddr, nodeID string) (*Config, error) {
conf := &Config{}
conf.InstanceName = nodeID
conf.ListenPeerURLs = append(conf.ListenPeerURLs,
@@ -64,7 +65,7 @@ func GenerateConfig(configDir, ipAddr, nodeID string) (*Config, error) {
// GenerateConfigFromExistingCluster automatically generates a config for joining an existing cluster
func GenerateConfigFromExistingCluster(Context EtcdMgrContext, configDir, ipAddr, nodeID string) (*Config, error) {
conf, err := GenerateConfig(configDir, ipAddr, nodeID)
conf, err := generateConfig(configDir, ipAddr, nodeID)
if err != nil {
return nil, err
}
@@ -81,10 +82,10 @@ func GenerateConfigFromExistingCluster(Context EtcdMgrContext, configDir, ipAddr
return conf, nil
}
func getURLFromSchemeIPPort(scheme, ip, port string) url.URL {
func getURLFromSchemeIPPort(scheme, ip string, port int) url.URL {
u := url.URL{
Scheme: scheme,
Host: ip + ":" + port,
Host: fmt.Sprintf("%s:%d", ip, port),
}
return u
}
+10 -20
View File
@@ -15,21 +15,8 @@ import (
"golang.org/x/net/context/ctxhttp"
)
// QuorumFormed tries to connect to the etcd cluster. If it fails it interprets it as an incomplete quorum.
func QuorumFormed(currentNodes []string) bool {
_, err := client.New(client.Config{
Endpoints: currentNodes,
Transport: client.DefaultTransport,
HeaderTimeoutPerRequest: DefaultClientTimeout,
})
if err != nil {
return false
}
return true
}
func IsQuorumFull(token string) (bool, error, []string) {
res, err := QueryDiscoveryService(token + "/_config/size")
func isQuorumFull(token string) (bool, error, []string) {
res, err := queryDiscoveryService(token + "/_config/size")
if err != nil {
return false, fmt.Errorf("cannot get discovery url cluster size: %v", err), []string{}
}
@@ -47,8 +34,8 @@ func IsQuorumFull(token string) (bool, error, []string) {
}
// QueryDiscoveryService reads a key from a discovery url.
func QueryDiscoveryService(token string) (*store.Event, error) {
// queryDiscoveryService reads a key from a discovery url.
func queryDiscoveryService(token string) (*store.Event, error) {
ctx, _ := context.WithTimeout(context.Background(), DefaultClientTimeout)
resp, err := ctxhttp.Get(ctx, http.DefaultClient, token)
if err != nil {
@@ -70,25 +57,28 @@ func QueryDiscoveryService(token string) (*store.Event, error) {
}
func GetCurrentNodesFromDiscovery(token string) ([]string, error) {
res, err := QueryDiscoveryService(token)
res, err := queryDiscoveryService(token)
if err != nil {
return nil, err
}
nodes := make([]string, 0, len(res.Node.Nodes))
for _, nn := range res.Node.Nodes {
if nn.Value == nil {
log.Printf("Skipping %q because no value exists", nn.Key)
}
clientPort, _ := strconv.Atoi(DefaultClientPort)
n, err := newDiscoveryNode(*nn.Value, clientPort)
n, err := newDiscoveryNode(*nn.Value, DefaultClientPort)
if err != nil {
log.Printf("invalid peer url %q in discovery service: %v", *nn.Value, err)
continue
}
for _, node := range n {
nodes = append(nodes, node)
}
}
return nodes, nil
}
@@ -12,7 +12,6 @@ import (
"github.com/quantum/castle/pkg/clusterd"
"github.com/quantum/castle/pkg/clusterd/inventory"
"github.com/quantum/castle/pkg/etcdmgr/bootstrap"
"github.com/quantum/castle/pkg/etcdmgr/manager"
"github.com/quantum/castle/pkg/etcdmgr/policy"
ctx "golang.org/x/net/context"
)
@@ -185,7 +184,7 @@ func (e *etcdMgrLeader) shrinkEtcdQuorum(context *clusterd.Context, candidates [
return fmt.Errorf("error in removing desired key for node: %+v err: %+v\n", candidate, err)
}
err = manager.RemoveMember(e.context, targetEndpoint)
err = RemoveMember(e.context, targetEndpoint)
if err != nil {
return fmt.Errorf("error in removing a member from the cluster. %+v\n", err)
}
-149
View File
@@ -1,149 +0,0 @@
package manager
import (
"errors"
"fmt"
"log"
"os"
"github.com/quantum/castle/pkg/etcdmgr/bootstrap"
ctx "golang.org/x/net/context"
)
// GetEtcdClients bootstraps an embedded etcd instance and returns a list of
// current etcd cluster's client URLs. (entrypoint, when it's used as a library)
func GetEtcdClients(configDir, token, ipAddr, nodeID string) ([]string, error) {
// GODEBUG setting forcing use of Go's resolver
os.Setenv("GODEBUG", "netdns=go+1")
full, err, currentNodes := bootstrap.IsQuorumFull(token)
//currentNodes, err := bootstrap.GetCurrentNodesFromDiscovery(token)
if err != nil {
return []string{}, errors.New("error querying discovery service")
}
log.Println("current etcd cluster nodes: ", currentNodes)
localURL := "http://" + ipAddr + ":" + bootstrap.DefaultClientPort
// Is it a restart scenario?
restart := false
log.Println("current localURL: ", localURL)
for _, node := range currentNodes {
if node == localURL {
log.Println("restart scenario detected.")
restart = true
}
}
if full && !restart {
log.Println("quorum is already formed, returning current cluster members: ", currentNodes)
return currentNodes, nil
}
log.Println("quorum is not complete, creating a new embedded etcd member...")
conf, err := bootstrap.GenerateConfig(configDir, ipAddr, nodeID)
if err != nil {
return []string{}, err
}
log.Println("conf:", conf)
factory := bootstrap.EmbeddedEtcdFactory{}
ee, err := factory.NewEmbeddedEtcd(token, conf, true)
if err != nil {
return nil, err
}
return ee.Server.Cluster().ClientURLs(), nil
}
// GetEtcdClientsWithConfig bootstraps an embedded etcd instance based on a given config and returns
// a list of current etcd cluster's client urls. (used in CLI scenarios).
func GetEtcdClientsWithConfig(token string, config bootstrap.Config) ([]string, error) {
// GODEBUG setting forcing use of Go's resolver
os.Setenv("GODEBUG", "netdns=go+1")
full, err, currentNodes := bootstrap.IsQuorumFull(token)
if err != nil {
return []string{}, errors.New("error in querying discovery service")
}
//TODO refactor + we assumed we only have one advertiseClient
localURL := "http://" + config.AdvertiseClientURLs[0].Host
// Is it a restart scenario?
restart := false
log.Println("current localURL: ", localURL)
for _, node := range currentNodes {
if node == localURL {
log.Println("restart scenario detected.")
restart = true
}
}
if full && !restart {
log.Println("quorum is full, returning current quorum nodes...")
log.Println("current nodes: ", currentNodes)
return currentNodes, nil
}
log.Println("creating a new embedded etcd")
factory := bootstrap.EmbeddedEtcdFactory{}
ee, err := factory.NewEmbeddedEtcd(token, &config, true)
if err != nil {
return nil, err
}
return ee.Server.Cluster().ClientURLs(), nil
}
// AddMember adds a node to the etcd cluster as a member
func AddMember(context bootstrap.EtcdMgrContext, node string) error {
log.Println("Adding a node to the etcd cluster as a member: ", node)
mapi, err := context.MembersAPI()
if err != nil {
return fmt.Errorf("error in Context.MembersAPI(). %+v", err)
}
member, err := mapi.Add(ctx.Background(), node)
if err != nil {
return err
}
log.Println("New member added: ", *member)
return nil
}
// RemoveMember removes a member from the etcd cluster
func RemoveMember(context bootstrap.EtcdMgrContext, node string) error {
log.Println("removing a member from the etcd cluster: ", node)
mapi, err := context.MembersAPI()
if err != nil {
return err
}
// Get the list of members.
members, err := mapi.List(ctx.Background())
if err != nil {
return err
}
log.Println("members: ", members)
var nodeID string
for _, m := range members {
// PeerURLs is taken from the etcd's terminology. It means what endpoints a member use to communicates by its peers.
// In our implementation, we always use 1 endpoint for each member. In some cases, someone could bind to localhost
// too which is not useful for us and we don't do that.
if sliceContains(m.PeerURLs, node) {
nodeID = m.ID
}
}
if nodeID == "" {
return errors.New("node not found")
}
err = mapi.Remove(ctx.Background(), nodeID)
if err != nil {
return err
}
log.Println("New member removed: ", node)
return nil
}
func sliceContains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
+67
View File
@@ -0,0 +1,67 @@
package etcdmgr
import (
"errors"
"fmt"
"log"
"github.com/quantum/castle/pkg/etcdmgr/bootstrap"
ctx "golang.org/x/net/context"
)
// AddMember adds a node to the etcd cluster as a member
func AddMember(context bootstrap.EtcdMgrContext, node string) error {
log.Println("Adding a node to the etcd cluster as a member: ", node)
mapi, err := context.MembersAPI()
if err != nil {
return fmt.Errorf("error in Context.MembersAPI(). %+v", err)
}
member, err := mapi.Add(ctx.Background(), node)
if err != nil {
return err
}
log.Println("New member added: ", *member)
return nil
}
// RemoveMember removes a member from the etcd cluster
func RemoveMember(context bootstrap.EtcdMgrContext, node string) error {
log.Println("removing a member from the etcd cluster: ", node)
mapi, err := context.MembersAPI()
if err != nil {
return err
}
// Get the list of members.
members, err := mapi.List(ctx.Background())
if err != nil {
return err
}
log.Println("members: ", members)
var nodeID string
for _, m := range members {
// PeerURLs is taken from the etcd's terminology. It means what endpoints a member use to communicates by its peers.
// In our implementation, we always use 1 endpoint for each member. In some cases, someone could bind to localhost
// too which is not useful for us and we don't do that.
if sliceContains(m.PeerURLs, node) {
nodeID = m.ID
}
}
if nodeID == "" {
return errors.New("node not found")
}
err = mapi.Remove(ctx.Background(), nodeID)
if err != nil {
return err
}
log.Println("New member removed: ", node)
return nil
}
func sliceContains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
+2
View File
@@ -33,6 +33,7 @@ func ChooseEtcdCandidatesToAddOrRemove(delta int, currentEtcdNodes, currentNodes
sort.Strings(candidates)
return candidates[:numOfNodeToRemove], nil
}
//need more candidates from etcd nodes
numOfNodeToRemove = numOfNodeToRemove - len(candidates)
moreCandidates := util.SetDifference(currentEtcdNodes, candidates).ToSlice()
@@ -44,6 +45,7 @@ func ChooseEtcdCandidatesToAddOrRemove(delta int, currentEtcdNodes, currentNodes
}
candidates = append(candidates, moreCandidates...)
}
sort.Strings(candidates)
return candidates, nil
}