Embedded etcd
This monster patch embeds the etcd server. It took a good deal of iterative work to tweak small details, and survived a rewrite from the initial etcd v2 API implementation to the beta version of v3. It has a notable race, and is missing some features, but it is ready for git master and external developer consumption.
This commit is contained in:
@@ -201,7 +201,7 @@ sudo mkdir -p /etc/systemd/system/mgmt.service.d/
|
|||||||
|
|
||||||
cat > /etc/systemd/system/mgmt.service.d/env.conf <<EOF
|
cat > /etc/systemd/system/mgmt.service.d/env.conf <<EOF
|
||||||
# Environment variables:
|
# Environment variables:
|
||||||
MGMT_SEED_ENDPOINT=http://127.0.0.1:2379
|
MGMT_SEEDS=http://127.0.0.1:2379
|
||||||
MGMT_CONVERGED_TIMEOUT=-1
|
MGMT_CONVERGED_TIMEOUT=-1
|
||||||
MGMT_MAX_RUNTIME=0
|
MGMT_MAX_RUNTIME=0
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ If you have a well phrased question that might benefit others, consider asking i
|
|||||||
## Quick start:
|
## Quick start:
|
||||||
* Either get the golang dependencies on your own, or run `make deps` if you're comfortable with how we install them.
|
* Either get the golang dependencies on your own, or run `make deps` if you're comfortable with how we install them.
|
||||||
* Run `make build` to get a freshly built `mgmt` binary.
|
* Run `make build` to get a freshly built `mgmt` binary.
|
||||||
* Run `cd $(mktemp --tmpdir -d tmp.XXX) && etcd` to get etcd running. The `mgmt` software will do this automatically for you in the future.
|
|
||||||
* Run `time ./mgmt run --file examples/graph0.yaml --converged-timeout=1` to try out a very simple example!
|
* Run `time ./mgmt run --file examples/graph0.yaml --converged-timeout=1` to try out a very simple example!
|
||||||
* To run continuously in the default mode of operation, omit the `--converged-timeout` option.
|
* To run continuously in the default mode of operation, omit the `--converged-timeout` option.
|
||||||
* Have fun hacking on our future technology!
|
* Have fun hacking on our future technology!
|
||||||
|
|||||||
99
config.go
99
config.go
@@ -85,7 +85,7 @@ func ParseConfigFromFile(filename string) *GraphConfig {
|
|||||||
|
|
||||||
// NewGraphFromConfig returns a new graph from existing input, such as from the
|
// NewGraphFromConfig returns a new graph from existing input, such as from the
|
||||||
// existing graph, and a GraphConfig struct.
|
// existing graph, and a GraphConfig struct.
|
||||||
func (g *Graph) NewGraphFromConfig(config *GraphConfig, etcdO *EtcdWObject, hostname string, noop bool) (*Graph, error) {
|
func (g *Graph) NewGraphFromConfig(config *GraphConfig, embdEtcd *EmbdEtcd, hostname string, noop bool) (*Graph, error) {
|
||||||
|
|
||||||
var graph *Graph // new graph to return
|
var graph *Graph // new graph to return
|
||||||
if g == nil { // FIXME: how can we check for an empty graph?
|
if g == nil { // FIXME: how can we check for an empty graph?
|
||||||
@@ -102,7 +102,7 @@ func (g *Graph) NewGraphFromConfig(config *GraphConfig, etcdO *EtcdWObject, host
|
|||||||
graph.SetName(config.Graph) // set graph name
|
graph.SetName(config.Graph) // set graph name
|
||||||
|
|
||||||
var keep []*Vertex // list of vertex which are the same in new graph
|
var keep []*Vertex // list of vertex which are the same in new graph
|
||||||
|
var resources []Res // list of resources to export
|
||||||
// use reflection to avoid duplicating code... better options welcome!
|
// use reflection to avoid duplicating code... better options welcome!
|
||||||
value := reflect.Indirect(reflect.ValueOf(config.Resources))
|
value := reflect.Indirect(reflect.ValueOf(config.Resources))
|
||||||
vtype := value.Type()
|
vtype := value.Type()
|
||||||
@@ -118,91 +118,106 @@ func (g *Graph) NewGraphFromConfig(config *GraphConfig, etcdO *EtcdWObject, host
|
|||||||
}
|
}
|
||||||
for j := 0; j < slice.Len(); j++ { // loop through resources of same kind
|
for j := 0; j < slice.Len(); j++ { // loop through resources of same kind
|
||||||
x := slice.Index(j).Interface()
|
x := slice.Index(j).Interface()
|
||||||
obj, ok := x.(Res) // convert to Res type
|
res, ok := x.(Res) // convert to Res type
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Error: Config: Can't convert: %v of type: %T to Res.", x, x)
|
return nil, fmt.Errorf("Error: Config: Can't convert: %v of type: %T to Res.", x, x)
|
||||||
}
|
}
|
||||||
if noop {
|
if noop {
|
||||||
obj.Meta().Noop = noop
|
res.Meta().Noop = noop
|
||||||
}
|
}
|
||||||
if _, exists := lookup[kind]; !exists {
|
if _, exists := lookup[kind]; !exists {
|
||||||
lookup[kind] = make(map[string]*Vertex)
|
lookup[kind] = make(map[string]*Vertex)
|
||||||
}
|
}
|
||||||
// XXX: should we export based on a @@ prefix, or a metaparam
|
// XXX: should we export based on a @@ prefix, or a metaparam
|
||||||
// like exported => true || exported => (host pattern)||(other pattern?)
|
// like exported => true || exported => (host pattern)||(other pattern?)
|
||||||
if !strings.HasPrefix(obj.GetName(), "@@") { // exported resource
|
if !strings.HasPrefix(res.GetName(), "@@") { // not exported resource
|
||||||
// XXX: we don't have a way of knowing if any of the
|
// XXX: we don't have a way of knowing if any of the
|
||||||
// metaparams are undefined, and as a result to set the
|
// metaparams are undefined, and as a result to set the
|
||||||
// defaults that we want! I hate the go yaml parser!!!
|
// defaults that we want! I hate the go yaml parser!!!
|
||||||
v := graph.GetVertexMatch(obj)
|
v := graph.GetVertexMatch(res)
|
||||||
if v == nil { // no match found
|
if v == nil { // no match found
|
||||||
obj.Init()
|
res.Init()
|
||||||
v = NewVertex(obj)
|
v = NewVertex(res)
|
||||||
graph.AddVertex(v) // call standalone in case not part of an edge
|
graph.AddVertex(v) // call standalone in case not part of an edge
|
||||||
}
|
}
|
||||||
lookup[kind][obj.GetName()] = v // used for constructing edges
|
lookup[kind][res.GetName()] = v // used for constructing edges
|
||||||
keep = append(keep, v) // append
|
keep = append(keep, v) // append
|
||||||
|
|
||||||
} else if !noop { // do not export any resources if noop
|
} else if !noop { // do not export any resources if noop
|
||||||
// XXX: do this in a different function...
|
// store for addition to etcd storage...
|
||||||
// add to etcd storage...
|
res.SetName(res.GetName()[2:]) //slice off @@
|
||||||
obj.SetName(obj.GetName()[2:]) //slice off @@
|
res.setKind(kind) // cheap init
|
||||||
|
resources = append(resources, res)
|
||||||
data, err := ResToB64(obj)
|
}
|
||||||
if err != nil {
|
}
|
||||||
return nil, fmt.Errorf("Config: Could not encode %v resource: %v, error: %v", kind, obj.GetName(), err)
|
}
|
||||||
|
// store in etcd
|
||||||
|
if err := EtcdSetResources(embdEtcd, hostname, resources); err != nil {
|
||||||
|
return nil, fmt.Errorf("Config: Could not export resources: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !etcdO.EtcdPut(hostname, obj.GetName(), kind, data) {
|
// lookup from etcd
|
||||||
return nil, fmt.Errorf("Config: Could not export %v resource: %v", kind, obj.GetName())
|
var hostnameFilter []string // empty to get from everyone
|
||||||
}
|
kindFilter := []string{}
|
||||||
}
|
for _, t := range config.Collector {
|
||||||
}
|
// XXX: should we just drop these everywhere and have the kind strings be all lowercase?
|
||||||
}
|
kind := FirstToUpper(t.Kind)
|
||||||
|
kindFilter = append(kindFilter, kind)
|
||||||
// lookup from etcd graph
|
}
|
||||||
// do all the graph look ups in one single step, so that if the etcd
|
// do all the graph look ups in one single step, so that if the etcd
|
||||||
// database changes, we don't have a partial state of affairs...
|
// database changes, we don't have a partial state of affairs...
|
||||||
nodes, ok := etcdO.EtcdGet()
|
if len(kindFilter) > 0 { // if kindFilter is empty, don't need to do lookups!
|
||||||
if ok {
|
var err error
|
||||||
|
resources, err = EtcdGetResources(embdEtcd, hostnameFilter, kindFilter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Config: Could not collect resources: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, res := range resources {
|
||||||
|
matched := false
|
||||||
|
// see if we find a collect pattern that matches
|
||||||
for _, t := range config.Collector {
|
for _, t := range config.Collector {
|
||||||
// XXX: should we just drop these everywhere and have the kind strings be all lowercase?
|
// XXX: should we just drop these everywhere and have the kind strings be all lowercase?
|
||||||
kind := FirstToUpper(t.Kind)
|
kind := FirstToUpper(t.Kind)
|
||||||
|
|
||||||
// use t.Kind and optionally t.Pattern to collect from etcd storage
|
// use t.Kind and optionally t.Pattern to collect from etcd storage
|
||||||
log.Printf("Collect: %v; Pattern: %v", kind, t.Pattern)
|
log.Printf("Collect: %v; Pattern: %v", kind, t.Pattern)
|
||||||
for _, str := range etcdO.EtcdGetProcess(nodes, kind) {
|
|
||||||
obj, err := B64ToRes(str)
|
// XXX: expand to more complex pattern matching here...
|
||||||
if err != nil {
|
if res.Kind() != kind {
|
||||||
log.Printf("B64ToRes failed to decode: %v", err)
|
|
||||||
log.Printf("Collect: %v: not collected!", kind)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
// we've already matched this resource, should we match again?
|
||||||
|
log.Printf("Config: Warning: Matching %v[%v] again!", kind, res.GetName())
|
||||||
|
}
|
||||||
|
matched = true
|
||||||
|
|
||||||
// collect resources but add the noop metaparam
|
// collect resources but add the noop metaparam
|
||||||
if noop {
|
if noop {
|
||||||
obj.Meta().Noop = noop
|
res.Meta().Noop = noop
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Pattern != "" { // XXX: simplistic for now
|
if t.Pattern != "" { // XXX: simplistic for now
|
||||||
obj.CollectPattern(t.Pattern) // obj.Dirname = t.Pattern
|
res.CollectPattern(t.Pattern) // res.Dirname = t.Pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Collect: %v[%v]: collected!", kind, obj.GetName())
|
log.Printf("Collect: %v[%v]: collected!", kind, res.GetName())
|
||||||
|
|
||||||
// XXX: similar to other resource add code:
|
// XXX: similar to other resource add code:
|
||||||
if _, exists := lookup[kind]; !exists {
|
if _, exists := lookup[kind]; !exists {
|
||||||
lookup[kind] = make(map[string]*Vertex)
|
lookup[kind] = make(map[string]*Vertex)
|
||||||
}
|
}
|
||||||
v := graph.GetVertexMatch(obj)
|
v := graph.GetVertexMatch(res)
|
||||||
if v == nil { // no match found
|
if v == nil { // no match found
|
||||||
obj.Init() // initialize go channels or things won't work!!!
|
res.Init() // initialize go channels or things won't work!!!
|
||||||
v = NewVertex(obj)
|
v = NewVertex(res)
|
||||||
graph.AddVertex(v) // call standalone in case not part of an edge
|
graph.AddVertex(v) // call standalone in case not part of an edge
|
||||||
}
|
}
|
||||||
lookup[kind][obj.GetName()] = v // used for constructing edges
|
lookup[kind][res.GetName()] = v // used for constructing edges
|
||||||
keep = append(keep, v) // append
|
keep = append(keep, v) // append
|
||||||
}
|
|
||||||
|
//break // let's see if another resource even matches
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
examples/etcd1d.yaml
Normal file
18
examples/etcd1d.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
graph: mygraph
|
||||||
|
resources:
|
||||||
|
file:
|
||||||
|
- name: file1d
|
||||||
|
path: "/tmp/mgmtD/f1d"
|
||||||
|
content: |
|
||||||
|
i am f1
|
||||||
|
state: exists
|
||||||
|
- name: "@@file2d"
|
||||||
|
path: "/tmp/mgmtD/f2d"
|
||||||
|
content: |
|
||||||
|
i am f2, exported from host D
|
||||||
|
state: exists
|
||||||
|
collect:
|
||||||
|
- kind: file
|
||||||
|
pattern: "/tmp/mgmtD/"
|
||||||
|
edges: []
|
||||||
157
main.go
157
main.go
@@ -19,6 +19,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
etcdtypes "github.com/coreos/etcd/pkg/types"
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -35,7 +36,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DEBUG = false
|
DEBUG = false // add additional log messages
|
||||||
|
TRACE = false // add execution flow log messages
|
||||||
|
VERBOSE = false // add extra log message output
|
||||||
)
|
)
|
||||||
|
|
||||||
// signal handler
|
// signal handler
|
||||||
@@ -59,16 +62,51 @@ func waitForSignal(exit chan bool) {
|
|||||||
|
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
var start = time.Now().UnixNano()
|
var start = time.Now().UnixNano()
|
||||||
var wg sync.WaitGroup
|
|
||||||
exit := make(chan bool) // exit signal
|
|
||||||
log.Printf("This is: %v, version: %v", program, version)
|
log.Printf("This is: %v, version: %v", program, version)
|
||||||
log.Printf("Main: Start: %v", start)
|
log.Printf("Main: Start: %v", start)
|
||||||
var G, fullGraph *Graph
|
|
||||||
|
|
||||||
if c.IsSet("file") && c.IsSet("puppet") {
|
hostname := c.String("hostname")
|
||||||
log.Println("the --file and --puppet parameters cannot be used together")
|
if hostname == "" {
|
||||||
|
hostname, _ = os.Hostname()
|
||||||
|
}
|
||||||
|
noop := c.Bool("noop")
|
||||||
|
|
||||||
|
seeds, err := etcdtypes.NewURLs(
|
||||||
|
FlattenListWithSplit(c.StringSlice("seeds"), []string{",", ";", " "}),
|
||||||
|
)
|
||||||
|
if err != nil && len(c.StringSlice("seeds")) > 0 {
|
||||||
|
log.Printf("Main: Error: seeds didn't parse correctly!")
|
||||||
return cli.NewExitError("", 1)
|
return cli.NewExitError("", 1)
|
||||||
}
|
}
|
||||||
|
clientURLs, err := etcdtypes.NewURLs(
|
||||||
|
FlattenListWithSplit(c.StringSlice("client-urls"), []string{",", ";", " "}),
|
||||||
|
)
|
||||||
|
if err != nil && len(c.StringSlice("client-urls")) > 0 {
|
||||||
|
log.Printf("Main: Error: clientURLs didn't parse correctly!")
|
||||||
|
return cli.NewExitError("", 1)
|
||||||
|
}
|
||||||
|
serverURLs, err := etcdtypes.NewURLs(
|
||||||
|
FlattenListWithSplit(c.StringSlice("server-urls"), []string{",", ";", " "}),
|
||||||
|
)
|
||||||
|
if err != nil && len(c.StringSlice("server-urls")) > 0 {
|
||||||
|
log.Printf("Main: Error: serverURLs didn't parse correctly!")
|
||||||
|
return cli.NewExitError("", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
idealClusterSize := uint16(c.Int("ideal-cluster-size"))
|
||||||
|
if idealClusterSize < 1 {
|
||||||
|
log.Printf("Main: Error: idealClusterSize should be at least one!")
|
||||||
|
return cli.NewExitError("", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("file") && c.IsSet("puppet") {
|
||||||
|
log.Println("Main: Error: the --file and --puppet parameters cannot be used together!")
|
||||||
|
return cli.NewExitError("", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
exit := make(chan bool) // exit signal
|
||||||
|
var G, fullGraph *Graph
|
||||||
|
|
||||||
// exit after `max-runtime` seconds for no reason at all...
|
// exit after `max-runtime` seconds for no reason at all...
|
||||||
if i := c.Int("max-runtime"); i > 0 {
|
if i := c.Int("max-runtime"); i > 0 {
|
||||||
@@ -88,28 +126,26 @@ func run(c *cli.Context) error {
|
|||||||
)
|
)
|
||||||
go converger.Loop(true) // main loop for converger, true to start paused
|
go converger.Loop(true) // main loop for converger, true to start paused
|
||||||
|
|
||||||
// initial etcd peer endpoint
|
// embedded etcd
|
||||||
seed := c.String("seed")
|
if len(seeds) == 0 {
|
||||||
if seed == "" {
|
log.Printf("Main: Seeds: No seeds specified!")
|
||||||
// XXX: start up etcd server, others will join me!
|
} else {
|
||||||
seed = "http://127.0.0.1:2379" // thus we use the local server!
|
log.Printf("Main: Seeds(%v): %v", len(seeds), seeds)
|
||||||
}
|
}
|
||||||
// then, connect to `seed` as a client
|
EmbdEtcd := NewEmbdEtcd(
|
||||||
|
hostname,
|
||||||
// FIXME: validate seed, or wait for it to fail in etcd init?
|
seeds,
|
||||||
|
clientURLs,
|
||||||
// etcd
|
serverURLs,
|
||||||
etcdO := &EtcdWObject{
|
c.Bool("no-server"),
|
||||||
seed: seed,
|
idealClusterSize,
|
||||||
converger: converger,
|
converger,
|
||||||
|
)
|
||||||
|
if err := EmbdEtcd.Startup(); err != nil { // startup (returns when etcd main loop is running)
|
||||||
|
log.Printf("Main: Etcd: Startup failed: %v", err)
|
||||||
|
exit <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname := c.String("hostname")
|
|
||||||
if hostname == "" {
|
|
||||||
hostname, _ = os.Hostname() // etcd watch key // XXX: this is not the correct key name this is the set key name... WOOPS
|
|
||||||
}
|
|
||||||
noop := c.Bool("noop")
|
|
||||||
|
|
||||||
exitchan := make(chan Event) // exit event
|
exitchan := make(chan Event) // exit event
|
||||||
go func() {
|
go func() {
|
||||||
startchan := make(chan struct{}) // start signal
|
startchan := make(chan struct{}) // start signal
|
||||||
@@ -124,26 +160,23 @@ func run(c *cli.Context) error {
|
|||||||
puppetchan = time.Tick(time.Duration(interval) * time.Second)
|
puppetchan = time.Tick(time.Duration(interval) * time.Second)
|
||||||
}
|
}
|
||||||
log.Println("Etcd: Starting...")
|
log.Println("Etcd: Starting...")
|
||||||
etcdchan := etcdO.EtcdWatch()
|
etcdchan := EtcdWatch(EmbdEtcd)
|
||||||
first := true // first loop or not
|
first := true // first loop or not
|
||||||
for {
|
for {
|
||||||
log.Println("Main: Waiting...")
|
log.Println("Main: Waiting...")
|
||||||
select {
|
select {
|
||||||
case _ = <-startchan: // kick the loop once at start
|
case <-startchan: // kick the loop once at start
|
||||||
// pass
|
// pass
|
||||||
case msg := <-etcdchan:
|
|
||||||
switch msg {
|
case b := <-etcdchan:
|
||||||
// some types of messages we ignore...
|
if !b { // ignore the message
|
||||||
case etcdFoo, etcdBar:
|
|
||||||
continue
|
continue
|
||||||
// while others passthrough and cause a compile!
|
|
||||||
case etcdStart, etcdEvent:
|
|
||||||
// pass
|
|
||||||
default:
|
|
||||||
log.Fatal("Etcd: Unhandled message: ", msg)
|
|
||||||
}
|
}
|
||||||
case _ = <-puppetchan:
|
// everything else passes through to cause a compile!
|
||||||
|
|
||||||
|
case <-puppetchan:
|
||||||
// nothing, just go on
|
// nothing, just go on
|
||||||
|
|
||||||
case msg := <-configchan:
|
case msg := <-configchan:
|
||||||
if c.Bool("no-watch") || !msg {
|
if c.Bool("no-watch") || !msg {
|
||||||
continue // not ready to read config
|
continue // not ready to read config
|
||||||
@@ -174,7 +207,7 @@ func run(c *cli.Context) error {
|
|||||||
|
|
||||||
// build graph from yaml file on events (eg: from etcd)
|
// build graph from yaml file on events (eg: from etcd)
|
||||||
// we need the vertices to be paused to work on them
|
// we need the vertices to be paused to work on them
|
||||||
if newFullgraph, err := fullGraph.NewGraphFromConfig(config, etcdO, hostname, noop); err == nil { // keep references to all original elements
|
if newFullgraph, err := fullGraph.NewGraphFromConfig(config, EmbdEtcd, hostname, noop); err == nil { // keep references to all original elements
|
||||||
fullGraph = newFullgraph
|
fullGraph = newFullgraph
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Config: Error making new graph from config: %v", err)
|
log.Printf("Config: Error making new graph from config: %v", err)
|
||||||
@@ -215,11 +248,19 @@ func run(c *cli.Context) error {
|
|||||||
|
|
||||||
waitForSignal(exit) // pass in exit channel to watch
|
waitForSignal(exit) // pass in exit channel to watch
|
||||||
|
|
||||||
|
log.Println("Destroy...")
|
||||||
|
|
||||||
G.Exit() // tell all the children to exit
|
G.Exit() // tell all the children to exit
|
||||||
|
|
||||||
// tell inner main loop to exit
|
// tell inner main loop to exit
|
||||||
resp := NewResp()
|
resp := NewResp()
|
||||||
exitchan <- Event{eventExit, resp, "", false}
|
go func() { exitchan <- Event{eventExit, resp, "", false} }()
|
||||||
|
|
||||||
|
// cleanup etcd main loop last so it can process everything first
|
||||||
|
if err := EmbdEtcd.Destroy(); err != nil { // shutdown and cleanup etcd
|
||||||
|
log.Printf("Etcd exited poorly with: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
resp.ACKWait() // let inner main loop finish cleanly just in case
|
resp.ACKWait() // let inner main loop finish cleanly just in case
|
||||||
|
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
@@ -243,7 +284,11 @@ func main() {
|
|||||||
|
|
||||||
// un-hijack from capnslog...
|
// un-hijack from capnslog...
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
|
if VERBOSE {
|
||||||
capnslog.SetFormatter(capnslog.NewLogFormatter(os.Stderr, "(etcd) ", flags))
|
capnslog.SetFormatter(capnslog.NewLogFormatter(os.Stderr, "(etcd) ", flags))
|
||||||
|
} else {
|
||||||
|
capnslog.SetFormatter(capnslog.NewNilFormatter())
|
||||||
|
}
|
||||||
|
|
||||||
// test for sanity
|
// test for sanity
|
||||||
if program == "" || version == "" {
|
if program == "" || version == "" {
|
||||||
@@ -294,11 +339,35 @@ func main() {
|
|||||||
Usage: "hostname to use",
|
Usage: "hostname to use",
|
||||||
},
|
},
|
||||||
// if empty, it will startup a new server
|
// if empty, it will startup a new server
|
||||||
cli.StringFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "seed, s",
|
Name: "seeds, s",
|
||||||
Value: "",
|
Value: &cli.StringSlice{}, // empty slice
|
||||||
Usage: "default etc peer endpoint",
|
Usage: "default etc client endpoint",
|
||||||
EnvVar: "MGMT_SEED_ENDPOINT",
|
EnvVar: "MGMT_SEEDS",
|
||||||
|
},
|
||||||
|
// port 2379 and 4001 are common
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "client-urls",
|
||||||
|
Value: &cli.StringSlice{},
|
||||||
|
Usage: "list of URLs to listen on for client traffic",
|
||||||
|
EnvVar: "MGMT_CLIENT_URLS",
|
||||||
|
},
|
||||||
|
// port 2380 and 7001 are common
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "server-urls, peer-urls",
|
||||||
|
Value: &cli.StringSlice{},
|
||||||
|
Usage: "list of URLs to listen on for server (peer) traffic",
|
||||||
|
EnvVar: "MGMT_SERVER_URLS",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "no-server",
|
||||||
|
Usage: "do not let other servers peer with me",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "ideal-cluster-size",
|
||||||
|
Value: defaultIdealClusterSize,
|
||||||
|
Usage: "ideal number of server peers in cluster, only read by initial server",
|
||||||
|
EnvVar: "MGMT_IDEAL_CLUSTER_SIZE",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "converged-timeout, t",
|
Name: "converged-timeout, t",
|
||||||
|
|||||||
30
misc.go
30
misc.go
@@ -40,6 +40,15 @@ func StrInList(needle string, haystack []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Uint64KeyFromStrInMap(needle string, haystack map[uint64]string) (uint64, bool) {
|
||||||
|
for k, v := range haystack {
|
||||||
|
if v == needle {
|
||||||
|
return k, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
// remove any duplicate values in the list
|
// remove any duplicate values in the list
|
||||||
// possibly sub-optimal, O(n^2)? implementation
|
// possibly sub-optimal, O(n^2)? implementation
|
||||||
func StrRemoveDuplicatesInList(list []string) []string {
|
func StrRemoveDuplicatesInList(list []string) []string {
|
||||||
@@ -87,7 +96,7 @@ func ReverseStringList(in []string) []string {
|
|||||||
|
|
||||||
// return the sorted list of string keys in a map with string keys
|
// return the sorted list of string keys in a map with string keys
|
||||||
// NOTE: i thought it would be nice for this to use: map[string]interface{} but
|
// NOTE: i thought it would be nice for this to use: map[string]interface{} but
|
||||||
// it turns out that's not allowed. I know we don't have generics, but common!
|
// it turns out that's not allowed. I know we don't have generics, but come on!
|
||||||
func StrMapKeys(m map[string]string) []string {
|
func StrMapKeys(m map[string]string) []string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for k, _ := range m {
|
for k, _ := range m {
|
||||||
@@ -97,6 +106,15 @@ func StrMapKeys(m map[string]string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StrMapKeysUint64(m map[string]uint64) []string {
|
||||||
|
result := []string{}
|
||||||
|
for k, _ := range m {
|
||||||
|
result = append(result, k)
|
||||||
|
}
|
||||||
|
sort.Strings(result) // deterministic order
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// return the sorted list of bool values in a map with string values
|
// return the sorted list of bool values in a map with string values
|
||||||
func BoolMapValues(m map[string]bool) []bool {
|
func BoolMapValues(m map[string]bool) []bool {
|
||||||
result := []bool{}
|
result := []bool{}
|
||||||
@@ -117,6 +135,16 @@ func StrMapValues(m map[string]string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return the sorted list of string values in a map with string values
|
||||||
|
func StrMapValuesUint64(m map[uint64]string) []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, v := range m {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
sort.Strings(result) // deterministic order
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// return true if everyone is true
|
// return true if everyone is true
|
||||||
func BoolMapTrue(l []bool) bool {
|
func BoolMapTrue(l []bool) bool {
|
||||||
for _, b := range l {
|
for _, b := range l {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ type MetaParams struct {
|
|||||||
type Base interface {
|
type Base interface {
|
||||||
GetName() string // can't be named "Name()" because of struct field
|
GetName() string // can't be named "Name()" because of struct field
|
||||||
SetName(string)
|
SetName(string)
|
||||||
|
setKind(string)
|
||||||
Kind() string
|
Kind() string
|
||||||
Meta() *MetaParams
|
Meta() *MetaParams
|
||||||
AssociateData(Converger)
|
AssociateData(Converger)
|
||||||
@@ -162,7 +163,12 @@ func (obj *BaseRes) SetName(name string) {
|
|||||||
obj.Name = name
|
obj.Name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the kind of resource this is
|
// setKind sets the kind. This is used internally for exported resources.
|
||||||
|
func (obj *BaseRes) setKind(kind string) {
|
||||||
|
obj.kind = kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind returns the kind of resource this is
|
||||||
func (obj *BaseRes) Kind() string {
|
func (obj *BaseRes) Kind() string {
|
||||||
return obj.kind
|
return obj.kind
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user