New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add basic resharding record migration logic for new cluster (no GC yet) #21
Changes from 7 commits
da90fcb
444ca11
9a05503
9007cbf
9b1cf48
4dd6fc0
404c0dc
e0c6687
1461b8f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ import ( | |
"sync" | ||
"time" | ||
|
||
"github.com/buraksezer/consistent" | ||
"github.com/coreos/etcd/snap" | ||
|
||
"github.com/miekg/dns" | ||
|
@@ -48,6 +49,10 @@ type dnsStore struct { | |
// for sending http Cache requests | ||
cluster []string | ||
id int | ||
// for adding new cluster and migration | ||
config []jsonClusterInfo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This differs from what I originally thought of (with state temporarily maintained in dedicated thread instead of becoming part of the store state), but I'll also probably give this a pass for initial attempt. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that definitely sounds better, no need to change the store state. |
||
// to compute if a key should be migrated | ||
lookup *consistent.Consistent | ||
} | ||
|
||
func newDNSStore(snapshotter *snap.Snapshotter, proposeC chan<- string, commitC <-chan *commitInfo, errorC <-chan error, cluster []string, id int) *dnsStore { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"log" | ||
"math/rand" | ||
|
@@ -215,6 +216,69 @@ func serveHashServerHTTPAPI(store *hashServerStore, port int, done chan<- error) | |
w.WriteHeader(resp.StatusCode) | ||
}) | ||
|
||
router.HandleFunc("/clusterinfo", func(w http.ResponseWriter, r *http.Request) { | ||
if r.Method != "GET" { | ||
http.Error(w, "Method has to be PUT", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// form a list clusters, each cluster is a list of nsInfo of nodes in the cluster | ||
|
||
file, err := os.Open(*configFile) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer file.Close() | ||
fileContent, err := ioutil.ReadAll(file) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
io.WriteString(w, string(fileContent)) | ||
return | ||
}) | ||
|
||
// probably updateconfig is a bettre name | ||
// the current name is consistent with httpapi.go | ||
router.HandleFunc("/addcluster", func(w http.ResponseWriter, r *http.Request) { | ||
log.Println("add cluster") | ||
if r.Method != "PUT" { | ||
http.Error(w, "Method has to be PUT", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
body, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
log.Printf("Cannot read /addcluster body: %v\n", err) | ||
http.Error(w, "Bad PUT body", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
jsonClusters := make([]jsonClusterInfo, 0) | ||
err = json.Unmarshal(body, &jsonClusters) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// update cluster info in dnsStore | ||
store.clusters = intoClusterMap(jsonClusters) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Locks here too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes boss |
||
// update consistent | ||
cfg := consistent.Config{ | ||
PartitionCount: len(store.clusters), | ||
ReplicationFactor: 2, // We are forced to have number larger than 1 | ||
Load: 3, | ||
Hasher: hasher{}, | ||
} | ||
log.Println("Received cluster update, setting new config") | ||
store.lookup = consistent.New(nil, cfg) | ||
for _, cluster := range store.clusters { | ||
store.lookup.Add(clusterToken(cluster.token)) | ||
log.Println("New cluster:", cluster.token) | ||
} | ||
|
||
w.WriteHeader(http.StatusNoContent) | ||
}) | ||
|
||
go func() { | ||
if err := http.ListenAndServe(":"+strconv.Itoa(port), router); err != nil { | ||
done <- err | ||
|
@@ -253,7 +317,7 @@ func sendDNSMsgUntilSuccess(m *dns.Msg, servers []*nsInfo) (*dns.Msg, error) { | |
} | ||
|
||
// Returns true if through direct query we have all answers. Otherwise return false | ||
func tryDirectQuery(store *hashServerStore, batchList []batchedDNSQuestions, msg *dns.Msg) bool { | ||
func tryDirectQuery(store *hashServerStore, batchList []batchedDNSQuestions, msg *dns.Msg, rd bool) bool { | ||
var wg sync.WaitGroup | ||
|
||
var answerLock sync.Mutex // protects hasAllAnswers and msg | ||
|
@@ -266,7 +330,7 @@ func tryDirectQuery(store *hashServerStore, batchList []batchedDNSQuestions, msg | |
defer wg.Done() | ||
|
||
m := new(dns.Msg) | ||
m.RecursionDesired = true // Also seek recursive. See comment below for behavior implications | ||
m.RecursionDesired = rd // Also seek recursive. See comment below for behavior implications | ||
m.Question = *batch.questions | ||
|
||
// This will try all servers one by one on preference order until depletion of the list | ||
|
@@ -425,7 +489,7 @@ func serveHashServerUDPAPI(store *hashServerStore) { | |
mDirect := new(dns.Msg) | ||
mDirect.Id = r.Id | ||
mDirect.Question = append(mDirect.Question, r.Question...) | ||
if tryDirectQuery(store, batchList, mDirect) { | ||
if tryDirectQuery(store, batchList, mDirect, r.RecursionDesired) { | ||
w.WriteMsg(mDirect) | ||
return | ||
} | ||
|
@@ -445,14 +509,16 @@ func serveHashServerUDPAPI(store *hashServerStore) { | |
// The makeshift design is to have the server stop and does the migration manually. | ||
// However in real-world deployment we need to implement transparent migration without killing servers. | ||
|
||
var configFile *string | ||
|
||
func main() { | ||
rand.Seed(time.Now().Unix()) | ||
|
||
store := hashServerStore{ | ||
clusters: make(map[clusterToken]clusterInfo), | ||
} | ||
|
||
configFile := flag.String("config", "", "filename to load initial config") | ||
configFile = flag.String("config", "", "filename to load initial config") | ||
flag.Parse() | ||
|
||
if err := loadConfig(&store, *configFile); err != nil { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a small comment to explain why locks are not needed here (due to proposals done through the channel)