// Start fsm and create new pirates func Start() { listener = make(chan gotocol.Message) // listener for fsm if archaius.Conf.Population < 2 { log.Fatal("fsm: can't create less than 2 pirates") } // create map of channels and a name index to select randoml nodes from noodles = make(map[string]chan gotocol.Message, archaius.Conf.Population) pnames = make([]string, archaius.Conf.Population) // indexable name list log.Println("fsm: population", archaius.Conf.Population, "pirates") for i := 1; i <= archaius.Conf.Population; i++ { name := names.Make(archaius.Conf.Arch, "atlantic", "bermuda", "blackbeard", "pirate", i) noodles[name] = make(chan gotocol.Message) go pirate.Start(noodles[name]) } i := 0 msgcount := 1 start := time.Now() for name, noodle := range noodles { pnames[i] = name i++ // tell the pirate it's name and how to talk back to it's fsm // this must be the first message the pirate sees noodle <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext(), name} if edda.Logchan != nil { // tell the pirate to report itself and new edges to the logger noodle <- gotocol.Message{gotocol.Inform, edda.Logchan, time.Now(), gotocol.NilContext(), ""} msgcount = 2 } } log.Println("fsm: Talk amongst yourselves for", archaius.Conf.RunDuration) rand.Seed(int64(len(noodles))) for _, name := range pnames { // for each pirate tell them about two other random pirates noodle := noodles[name] // lookup the channel // pick a first random pirate to tell this one about talkto := pnames[rand.Intn(len(pnames))] noodle <- gotocol.Message{gotocol.NameDrop, noodles[talkto], time.Now(), gotocol.NewRequest(), talkto} // pick a second random pirate to tell this one about talkto = pnames[rand.Intn(len(pnames))] noodle <- gotocol.Message{gotocol.NameDrop, noodles[talkto], time.Now(), gotocol.NewRequest(), talkto} // anonymously send this pirate a random amount of GoldCoin up to 100 gold := fmt.Sprintf("%d", rand.Intn(100)) noodle <- gotocol.Message{gotocol.GoldCoin, nil, time.Now(), gotocol.NewRequest(), gold} // tell this pirate to start chatting with friends every 0.1 to 10 secs delay := fmt.Sprintf("%dms", 100+rand.Intn(9900)) noodle <- gotocol.Message{gotocol.Chat, nil, time.Now(), gotocol.NewRequest(), delay} } msgcount += 4 d := time.Since(start) log.Println("fsm: Delivered", msgcount*len(pnames), "messages in", d) shutdown() }
// Reload the network from a file func Reload(arch string) { listener = make(chan gotocol.Message) // listener for fsm log.Println("fsm reloading from " + arch + ".json") g := graphjson.ReadArch(arch) pop := 0 // count how many nodes there are for _, element := range g.Graph { if element.Node != "" { pop++ } } archaius.Conf.Population = pop // create the map of channels noodles = make(map[string]chan gotocol.Message, archaius.Conf.Population) // Start all the services for _, element := range g.Graph { if element.Node != "" && element.Service != "" { name := element.Node noodles[name] = make(chan gotocol.Message) // start the service and tell it it's name switch element.Service { case "pirate": go pirate.Start(noodles[name]) noodles[name] <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext(), name} if edda.Logchan != nil { // tell the pirate to report itself and new edges to the logger noodles[name] <- gotocol.Message{gotocol.Inform, edda.Logchan, time.Now(), gotocol.NilContext(), ""} } default: log.Println("fsm: unknown service: " + element.Service) } } } // Make all the connections for _, element := range g.Graph { if element.Edge != "" && element.Source != "" && element.Target != "" { noodles[element.Source] <- gotocol.Message{gotocol.NameDrop, noodles[element.Target], time.Now(), gotocol.NewRequest(), element.Target} log.Println("Link " + element.Source + " > " + element.Target) } } // send money and start the pirates chatting for _, noodle := range noodles { // same as below for now, but will save and read back from file later // anonymously send this pirate a random amount of GoldCoin up to 100 gold := fmt.Sprintf("%d", rand.Intn(100)) noodle <- gotocol.Message{gotocol.GoldCoin, nil, time.Now(), gotocol.NewRequest(), gold} // tell this pirate to start chatting with friends every 0.1 to 10 secs delay := fmt.Sprintf("%dms", 100+rand.Intn(9900)) noodle <- gotocol.Message{gotocol.Chat, nil, time.Now(), gotocol.NilContext(), delay} } shutdown() }
// Shutdown fsm and pirates func shutdown() { var msg gotocol.Message hist := collect.NewHist("fsm") // wait until the delay has finished if archaius.Conf.RunDuration >= time.Millisecond { time.Sleep(archaius.Conf.RunDuration) } log.Println("fsm: Shutdown") for _, noodle := range noodles { gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), "beer volcano"}.GoSend(noodle) } for len(noodles) > 0 { msg = <-listener collect.Measure(hist, time.Since(msg.Sent)) if archaius.Conf.Msglog { log.Printf("fsm: %v\n", msg) } switch msg.Imposition { case gotocol.Goodbye: delete(noodles, msg.Intention) if archaius.Conf.Msglog { log.Printf("fsm: Pirate population: %v \n", len(noodles)) } } } collect.Save() log.Println("fsm: Exit") }
// create eureka service registries in each zone func CreateEureka() { // setup name service and cross zone replication links znames := archaius.Conf.ZoneNames Create("eureka", EurekaPkg, archaius.Conf.Regions, 3) for n, ch := range eurekachan { var n1, n2 string switch names.Zone(n) { case znames[0]: n1 = znames[1] n2 = znames[2] case znames[1]: n1 = znames[0] n2 = znames[2] case znames[2]: n1 = znames[0] n2 = znames[1] } for nn, cch := range eurekachan { if names.Region(nn) == names.Region(n) && (names.Zone(nn) == n1 || names.Zone(nn) == n2) { //log.Println("Eureka cross connect from: " + n + " to " + nn) gotocol.Send(ch, gotocol.Message{gotocol.NameDrop, cch, time.Now(), gotocol.NilContext(), nn}) } } } }
// shut down the Eureka service registries and wait for them to go away func ShutdownEureka() { // shutdown eureka and wait to catch eureka reply //log.Println(eurekachan) for _, ch := range eurekachan { gotocol.Message{gotocol.Goodbye, listener, time.Now(), gotocol.NilContext(), "shutdown"}.GoSend(ch) } for _ = range eurekachan { <-listener } // wait for all the eureka to flush messages and exit eureka.Wg.Wait() }
// Test the discovery process by writing and reading back service information func TestDiscovery(t *testing.T) { fmt.Println("eureka_test start") listener := make(chan gotocol.Message) edda.Logchan = make(chan gotocol.Message, 10) // buffered channel archaius.Conf.Msglog = true archaius.Conf.GraphjsonFile = "test" archaius.Conf.GraphmlFile = "test" go edda.Start("test.edda") eureka := make(chan gotocol.Message, 10) go Start(eureka, "test.eureka") // stack up a series of requests in the buffered channel eureka <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext(), "test0" + " " + "test"} eureka <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext(), "test1" + " " + "test"} eureka <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext(), "thing0" + " " + "thing"} eureka <- gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NilContext(), "test0"} eureka <- gotocol.Message{gotocol.Goodbye, listener, time.Now(), gotocol.NilContext(), ""} // pick up responses until we see the Goodbye response for { msg := <-listener if archaius.Conf.Msglog { fmt.Printf("test_eureka: %v\n", msg) } if msg.Imposition == gotocol.Goodbye { break } switch msg.Imposition { case gotocol.GetResponse: if msg.Intention != "test" { t.Fail() } } } if edda.Logchan != nil { close(edda.Logchan) } //wait until edda and eureka finish flushing and close files Wg.Wait() edda.Wg.Wait() fmt.Println("eureka_test end") }
// Run migration for a while then shut down func Run(rootservice, victim string) { // tell denominator to start chatting with microservices every 0.01 secs delay := fmt.Sprintf("%dms", 10) log.Println(rootservice+" activity rate ", delay) SendToName(rootservice, gotocol.Message{gotocol.Chat, nil, time.Now(), gotocol.NilContext(), delay}) // wait until the delay has finished if archaius.Conf.RunDuration >= time.Millisecond { time.Sleep(archaius.Conf.RunDuration / 2) chaosmonkey.Delete(&noodles, victim) // kill a random victim half way through time.Sleep(archaius.Conf.RunDuration / 2) } log.Println("asgard: Shutdown") ShutdownNodes() ShutdownEureka() collect.Save() }
// shut down the nodes and wait for them to go away func ShutdownNodes() { for _, noodle := range noodles { gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), "shutdown"}.GoSend(noodle) } for len(noodles) > 0 { msg := <-listener if archaius.Conf.Msglog { log.Printf("asgard: %v\n", msg) } switch msg.Imposition { case gotocol.Goodbye: delete(noodles, msg.Intention) if archaius.Conf.Msglog { log.Printf("asgard: %v shutdown, population: %v \n", msg.Intention, len(noodles)) } } } }
// Start the pirate, all configuration and state is sent via messages func Start(listener chan gotocol.Message) { dunbar := 10 // starting point for how many buddies to remember // remember the channel to talk to named buddies buddies := make(map[string]chan gotocol.Message, dunbar) // remember who sent GoldCoin and how much, to buy favors benefactors := make(map[string]int, dunbar) var booty int // current GoldCoin balance var fsm chan gotocol.Message // remember how to talk back to creator var name string // remember my name var logger chan gotocol.Message // if set, send updates var chatrate time.Duration hist := collect.NewHist("") chatTicker := time.NewTicker(time.Hour) chatTicker.Stop() for { select { case msg := <-listener: collect.Measure(hist, time.Since(msg.Sent)) if archaius.Conf.Msglog { log.Printf("%v: %v\n", name, msg) } switch msg.Imposition { case gotocol.Hello: if name == "" { // if I don't have a name yet remember what I've been named fsm = msg.ResponseChan // remember how to talk to my namer name = msg.Intention // message body is my name hist = collect.NewHist(name) } case gotocol.Inform: // remember where to send updates logger = gotocol.InformHandler(msg, name, listener) case gotocol.NameDrop: // don't remember too many buddies and don't talk to myself buddy := msg.Intention // message body is buddy name if len(buddies) < dunbar && buddy != name { // remember how to talk to this buddy buddies[buddy] = msg.ResponseChan // message channel is buddy's listener if logger != nil { // if it's setup, tell the logger I have a new buddy to talk to logger <- gotocol.Message{gotocol.Inform, listener, time.Now(), gotocol.NilContext(), name + " " + buddy} } } case gotocol.Chat: // setup the ticker to run at the specified rate d, e := time.ParseDuration(msg.Intention) if e == nil && d >= time.Millisecond && d <= time.Hour { chatrate = d chatTicker = time.NewTicker(chatrate) // assume we got paid before we started chatting rand.Seed(int64(booty)) } case gotocol.GoldCoin: var coin int _, e := fmt.Sscanf(msg.Intention, "%d", &coin) if e == nil && coin > 0 { booty += coin for name, ch := range buddies { if ch == msg.ResponseChan { benefactors[name] += coin } } } case gotocol.Goodbye: if archaius.Conf.Msglog { log.Printf("%v: Going away with %v gold coins, chatting every %v\n", name, booty, chatrate) } gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), name}.GoSend(fsm) return } case <-chatTicker.C: if rand.Intn(100) < 50 { // 50% of the time // use Namedrop to tell the last buddy about the first var firstBuddyName string var firstBuddyChan, lastBuddyChan chan gotocol.Message if len(buddies) >= 2 { for name, ch := range buddies { if firstBuddyName == "" { firstBuddyName = name firstBuddyChan = ch } else { lastBuddyChan = ch } gotocol.Message{gotocol.NameDrop, firstBuddyChan, time.Now(), gotocol.NewRequest(), firstBuddyName}.GoSend(lastBuddyChan) } } } else { // send a buddy some money if booty > 0 { donation := rand.Intn(booty) luckyNumber := rand.Intn(len(buddies)) if donation > 0 { for _, ch := range buddies { if luckyNumber == 0 { gotocol.Message{gotocol.GoldCoin, listener, time.Now(), gotocol.NewRequest(), fmt.Sprintf("%d", donation)}.GoSend(ch) booty -= donation break } else { luckyNumber-- } } } } } } } }
// Start the elb, all configuration and state is sent via messages func Start(listener chan gotocol.Message) { dunbar := archaius.Conf.Population // remember the channel to talk to microservices microservices := make(map[string]chan gotocol.Message, dunbar) microindex := make([]chan gotocol.Message, dunbar) dependencies := make(map[string]time.Time, dunbar) // dependent services and time last updated var netflixoss, requestor chan gotocol.Message // remember how to talk back to creator var name string // remember my name eureka := make(map[string]chan gotocol.Message, 3) // service registry per zone var chatrate time.Duration ep, _ := time.ParseDuration(archaius.Conf.EurekaPoll) eurekaTicker := time.NewTicker(ep) hist := collect.NewHist("") chatTicker := time.NewTicker(time.Hour) chatTicker.Stop() for { select { case msg := <-listener: collect.Measure(hist, time.Since(msg.Sent)) if archaius.Conf.Msglog { log.Printf("%v: %v\n", name, msg) } switch msg.Imposition { case gotocol.Hello: if name == "" { // if I don't have a name yet remember what I've been named netflixoss = msg.ResponseChan // remember how to talk to my namer name = msg.Intention // message body is my name hist = collect.NewHist(name) } case gotocol.Inform: eureka[msg.Intention] = gotocol.InformHandler(msg, name, listener) case gotocol.NameDrop: // cross zone = true gotocol.NameDropHandler(&dependencies, µservices, msg, name, listener, eureka, true) case gotocol.Forget: // forget a buddy gotocol.ForgetHandler(&dependencies, µservices, msg) case gotocol.Chat: // setup the ticker to run at the specified rate d, e := time.ParseDuration(msg.Intention) if e == nil && d >= time.Millisecond && d <= time.Hour { chatrate = d chatTicker = time.NewTicker(chatrate) } case gotocol.GetRequest: // route the request on to microservices requestor = msg.ResponseChan // Intention body indicates which service to route to or which key to get // need to lookup service by type rather than randomly call one day if len(microservices) > 0 { if len(microindex) != len(microservices) { // rebuild index i := 0 for _, ch := range microservices { microindex[i] = ch i++ } } m := rand.Intn(len(microservices)) // pass on request to a random service gotocol.Message{gotocol.GetRequest, listener, time.Now(), msg.Ctx.NewSpan(), msg.Intention}.GoSend(microindex[m]) } case gotocol.GetResponse: // return path from a request, send payload back up if requestor != nil { gotocol.Message{gotocol.GetResponse, listener, time.Now(), msg.Ctx.NewSpan(), msg.Intention}.GoSend(requestor) } case gotocol.Put: // route the request on to a random dependency if len(microservices) > 0 { if len(microindex) != len(microservices) { // rebuild index i := 0 for _, ch := range microservices { microindex[i] = ch i++ } } m := rand.Intn(len(microservices)) // pass on request to a random service gotocol.Message{gotocol.Put, listener, time.Now(), msg.Ctx.NewSpan(), msg.Intention}.GoSend(microindex[m]) } case gotocol.Goodbye: if archaius.Conf.Msglog { log.Printf("%v: Going away, chatting every %v\n", name, chatrate) } gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), name}.GoSend(netflixoss) return } case <-eurekaTicker.C: // check to see if any new dependencies have appeared for dep, _ := range dependencies { for _, ch := range eureka { ch <- gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NilContext(), dep} } } case <-chatTicker.C: if len(microservices) > 0 { // build index if needed if len(microindex) != len(microservices) { i := 0 for _, ch := range microservices { microindex[i] = ch i++ } } m := rand.Intn(len(microservices)) // start a request to a random member of this elb gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NewRequest(), name}.GoSend(microindex[m]) } } } }
// connect to eureka services in every region func ConnectEveryEureka(name string) { for n, ch := range eurekachan { gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext(), n}) } }
// Start a node using the named package, and connect it to any dependencies func StartNode(name string, dependencies ...string) { if names.Package(name) == "eureka" { eurekachan[name] = make(chan gotocol.Message, archaius.Conf.Population) go eureka.Start(eurekachan[name], name) return } else { noodles[name] = make(chan gotocol.Message) } // start the service and tell it it's name switch names.Package(name) { case "pirate": go pirate.Start(noodles[name]) case "elb": go elb.Start(noodles[name]) case "denominator": go denominator.Start(noodles[name]) case "zuul": go zuul.Start(noodles[name]) case "karyon": go karyon.Start(noodles[name]) case "monolith": go monolith.Start(noodles[name]) case "staash": go staash.Start(noodles[name]) case "priamCassandra": go priamCassandra.Start(noodles[name]) case "store": go store.Start(noodles[name]) default: log.Fatal("asgard: unknown package: " + names.Package(name)) } noodles[name] <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext(), name} // there is a eureka service registry in each zone, so in-zone services just get to talk to their local registry // elb are cross zone, so need to see all registries in a region // denominator are cross region so need to see all registries globally // priamCassandra depends explicitly on eureka for cross region clusters crossregion := false for _, d := range dependencies { if d == "eureka" { crossregion = true } } for n, ch := range eurekachan { if names.Region(name) == "*" || crossregion { // need to know every eureka in all zones and regions gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext(), n}) } else { if names.Zone(name) == "*" && names.Region(name) == names.Region(n) { // need every eureka in my region gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext(), n}) } else { if names.RegionZone(name) == names.RegionZone(n) { // just the eureka in this specific zone gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext(), n}) } } } } //log.Println(dependencies) // pass on symbolic dependencies without channels that will be looked up in Eureka later for _, dep := range dependencies { if dep != "" && dep != "eureka" { // ignore special case of eureka in dependency list //log.Println(name + " depends on " + dep) gotocol.Send(noodles[name], gotocol.Message{gotocol.NameDrop, nil, time.Now(), gotocol.NilContext(), dep}) } } }
// Tell a source node how to connect to a target node directly by name, only used when Eureka can't be used func Connect(source, target string) { if noodles[source] != nil && noodles[target] != nil { gotocol.Send(noodles[source], gotocol.Message{gotocol.NameDrop, noodles[target], time.Now(), gotocol.NilContext(), target}) //log.Println("Link " + source + " > " + target) } else { log.Fatal("Asgard can't link " + source + " > " + target) } }
// Start store, all configuration and state is sent via messages func Start(listener chan gotocol.Message) { dunbar := 30 // starting point for how many nodes to remember // remember the channel to talk to microservices microservices := make(map[string]chan gotocol.Message, dunbar) dependencies := make(map[string]time.Time, dunbar) // dependent services and time last updated store := make(map[string]string, 4) // key value store store["why?"] = "because..." var netflixoss, requestor chan gotocol.Message // remember creator and how to talk back to incoming requests var name string // remember my name eureka := make(map[string]chan gotocol.Message, 3) // service registry per zone var chatrate time.Duration hist := collect.NewHist("") ep, _ := time.ParseDuration(archaius.Conf.EurekaPoll) eurekaTicker := time.NewTicker(ep) chatTicker := time.NewTicker(time.Hour) chatTicker.Stop() for { select { case msg := <-listener: collect.Measure(hist, time.Since(msg.Sent)) if archaius.Conf.Msglog { log.Printf("%v: %v\n", name, msg) } switch msg.Imposition { case gotocol.Hello: if name == "" { // if I don't have a name yet remember what I've been named netflixoss = msg.ResponseChan // remember how to talk to my namer name = msg.Intention // message body is my name hist = collect.NewHist(name) } case gotocol.Inform: eureka[msg.Intention] = gotocol.InformHandler(msg, name, listener) case gotocol.NameDrop: // cross zone = true gotocol.NameDropHandler(&dependencies, µservices, msg, name, listener, eureka, true) case gotocol.Forget: // forget a buddy gotocol.ForgetHandler(&dependencies, µservices, msg) case gotocol.Chat: // setup the ticker to run at the specified rate d, e := time.ParseDuration(msg.Intention) if e == nil && d >= time.Millisecond && d <= time.Hour { chatrate = d chatTicker = time.NewTicker(chatrate) } case gotocol.GetRequest: // return any stored value for this key (Cassandra READ.ONE behavior) gotocol.Message{gotocol.GetResponse, listener, time.Now(), msg.Ctx.NewSpan(), store[msg.Intention]}.GoSend(msg.ResponseChan) case gotocol.GetResponse: // return path from a request, send payload back up (not currently used) if requestor != nil { gotocol.Message{gotocol.GetResponse, listener, time.Now(), msg.Ctx.NewSpan(), msg.Intention}.GoSend(requestor) } case gotocol.Put: requestor = msg.ResponseChan // set a key value pair and replicate globally var key, value string fmt.Sscanf(msg.Intention, "%s%s", &key, &value) if key != "" && value != "" { store[key] = value // duplicate the request on to all connected store nodes if len(microservices) > 0 { // replicate request for _, c := range microservices { gotocol.Message{gotocol.Replicate, listener, time.Now(), msg.Ctx.NewSpan(), msg.Intention}.GoSend(c) } } } case gotocol.Replicate: // Replicate is only used between store nodes // end point for a request var key, value string fmt.Sscanf(msg.Intention, "%s%s", &key, &value) // log.Printf("store: %v:%v", key, value) if key != "" && value != "" { store[key] = value } // name looks like: netflixoss.us-east-1.zoneC.cassTurtle.store.cassTurtle11 myregion := names.Region(name) //log.Printf("%v: %v\n", name, myregion) // find if this was a cross region Replicate for n, c := range microservices { // find the name matching incoming request channel if c == msg.ResponseChan { if myregion != names.Region(n) { // Replicate from out of region needs to be Replicated only to other zones in this Region for nz, cz := range microservices { if myregion == names.Region(nz) { //log.Printf("%v rep to: %v\n", name, nz) gotocol.Message{gotocol.Replicate, listener, time.Now(), msg.Ctx.NewSpan(), msg.Intention}.GoSend(cz) } } } } } case gotocol.Goodbye: if archaius.Conf.Msglog { log.Printf("%v: Going away, zone: %v\n", name, store["zone"]) } gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), name}.GoSend(netflixoss) return } case <-eurekaTicker.C: // check to see if any new dependencies have appeared for dep, _ := range dependencies { for _, ch := range eureka { ch <- gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NilContext(), dep} } } case <-chatTicker.C: // nothing to do here at the moment } } }
// Start eureka discovery service and set name directly func Start(listener chan gotocol.Message, name string) { // use a waitgroup so whoever starts eureka can tell it's ready and when stopping that the logs have been flushed Wg.Add(1) defer Wg.Done() var msg gotocol.Message var ok bool hist := collect.NewHist(name) microservices := make(map[string]chan gotocol.Message, archaius.Conf.Dunbar) eurekaservices := make(map[string]chan gotocol.Message, 2) metadata := make(map[string]meta, archaius.Conf.Dunbar) lastrequest := make(map[callback]time.Time) // remember time of last request for a service from this requestor log.Println(name + ": starting") for { msg, ok = <-listener collect.Measure(hist, time.Since(msg.Sent)) if !ok { break // channel was closed } if archaius.Conf.Msglog { log.Printf("%v(backlog %v): %v\n", name, len(listener), msg) } switch msg.Imposition { // used to wire up connections to other eureka nodes only case gotocol.NameDrop: if msg.Intention != name { // don't talk to myself eurekaservices[msg.Intention] = msg.ResponseChan } // for new nodes record the data, replicate and maybe pass on to be logged case gotocol.Put: if microservices[msg.Intention] == nil { // ignore duplicate requests microservices[msg.Intention] = msg.ResponseChan metadata[msg.Intention] = meta{true, msg.Sent} // replicate request, everyone ends up with the same timestamp for state change of this service for _, c := range eurekaservices { gotocol.Message{gotocol.Replicate, msg.ResponseChan, msg.Sent, gotocol.NilContext(), msg.Intention}.GoSend(c) } if edda.Logchan != nil { edda.Logchan <- msg } } case gotocol.Replicate: if microservices[msg.Intention] == nil { // ignore multiple requests microservices[msg.Intention] = msg.ResponseChan metadata[msg.Intention] = meta{true, msg.Sent} } case gotocol.Inform: // don't store edges in discovery but do log them if edda.Logchan != nil { edda.Logchan <- msg } case gotocol.GetRequest: if msg.Intention == "" { log.Fatal(name + ": empty GetRequest") } if microservices[msg.Intention] != nil { // matched a unique full name gotocol.Message{gotocol.NameDrop, microservices[msg.Intention], time.Now(), gotocol.NilContext(), msg.Intention}.GoSend(msg.ResponseChan) break } for n, ch := range microservices { // respond with all the online names that match the service component if names.Service(n) == msg.Intention { // if there was an update for the looked up service since last check // log.Printf("%v: matching %v with %v, last: %v metadata: %v\n", name, n, msg.Intention, lastrequest[callback{n, msg.ResponseChan}], metadata[n].registered) if metadata[n].registered.After(lastrequest[callback{n, msg.ResponseChan}]) { if metadata[n].online { gotocol.Message{gotocol.NameDrop, ch, time.Now(), gotocol.NilContext(), n}.GoSend(msg.ResponseChan) } else { log.Printf("%v:Forget %v\n", name, n) gotocol.Message{gotocol.Forget, ch, time.Now(), gotocol.NilContext(), n}.GoSend(msg.ResponseChan) } } // remember for next time lastrequest[callback{n, msg.ResponseChan}] = msg.Sent } } case gotocol.Delete: // remove a node if microservices[msg.Intention] != nil { // matched a unique full name metadata[msg.Intention] = meta{false, time.Now()} // replicate request for _, c := range eurekaservices { gotocol.Message{gotocol.Replicate, nil, time.Now(), gotocol.NilContext(), msg.Intention}.GoSend(c) } if edda.Logchan != nil { edda.Logchan <- msg } } case gotocol.Goodbye: gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), name}.GoSend(msg.ResponseChan) log.Println(name + ": closing") return } } }
// Start the denominator, all configuration and state is sent via messages func Start(listener chan gotocol.Message) { dunbar := 6 // starting point for how many nodes to remember // remember the channel to talk to microservices microservices := make(map[string]chan gotocol.Message, dunbar) microindex := make([]chan gotocol.Message, dunbar) dependencies := make(map[string]time.Time, dunbar) // dependent services and time last updated var netflixoss chan gotocol.Message // remember how to talk back to creator var name string // remember my name hist := collect.NewHist("") // don't know name yet eureka := make(map[string]chan gotocol.Message, 3*archaius.Conf.Regions) // service registry per zone and region var chatrate time.Duration ep, _ := time.ParseDuration(archaius.Conf.EurekaPoll) eurekaTicker := time.NewTicker(ep) chatTicker := time.NewTicker(time.Hour) chatTicker.Stop() for { select { case msg := <-listener: collect.Measure(hist, time.Since(msg.Sent)) if archaius.Conf.Msglog { log.Printf("%v: %v\n", name, msg) } switch msg.Imposition { case gotocol.Hello: if name == "" { // if I don't have a name yet remember what I've been named netflixoss = msg.ResponseChan // remember how to talk to my namer name = msg.Intention // message body is my name hist = collect.NewHist(name) } case gotocol.Inform: eureka[msg.Intention] = gotocol.InformHandler(msg, name, listener) case gotocol.NameDrop: gotocol.NameDropHandler(&dependencies, µservices, msg, name, listener, eureka) case gotocol.Forget: // forget a buddy gotocol.ForgetHandler(&dependencies, µservices, msg) case gotocol.Chat: // setup the ticker to run at the specified rate d, e := time.ParseDuration(msg.Intention) if e == nil && d >= time.Millisecond && d <= time.Hour { chatrate = d chatTicker = time.NewTicker(chatrate) } case gotocol.GetResponse: // return path from a request // nothing to do at this level case gotocol.Goodbye: if archaius.Conf.Msglog { log.Printf("%v: Going away, was chatting every %v\n", name, chatrate) } gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext(), name}.GoSend(netflixoss) return } case <-eurekaTicker.C: // check to see if any new dependencies have appeared for dep, _ := range dependencies { for _, ch := range eureka { ch <- gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NilContext(), dep} } } case <-chatTicker.C: if len(microservices) > 0 { // build index if needed if len(microindex) != len(microservices) { i := 0 for _, ch := range microservices { microindex[i] = ch i++ } } m := rand.Intn(len(microservices)) // start a request to a random member of this denominator if rand.Intn(2) == 0 { gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NewRequest(), "why?"}.GoSend(microindex[m]) } else { gotocol.Message{gotocol.Put, listener, time.Now(), gotocol.NewRequest(), "remember me"}.GoSend(microindex[m]) } } } } }