etcd: Rewrite embed etcd implementation

This is a giant cleanup of the etcd code. The earlier version was
written when I was less experienced with golang.

This is still not perfect, and does contain some races, but at least
it's a decent base to start from. The automatic elastic clustering
should be considered an experimental feature. If you need a more
battle-tested cluster, then you should manage etcd manually and point
mgmt at your existing cluster.
This commit is contained in:
James Shubin
2018-05-05 17:35:08 -04:00
parent fb275d9537
commit a5842a41b2
56 changed files with 5459 additions and 2654 deletions

View File

@@ -18,6 +18,7 @@
package coreworld
import (
"context"
"fmt"
"github.com/purpleidea/mgmt/lang/funcs"
@@ -75,6 +76,8 @@ func (obj *ExchangeFunc) Init(init *interfaces.Init) error {
// Stream returns the changing values that this func has over time.
func (obj *ExchangeFunc) Stream() error {
defer close(obj.init.Output) // the sender closes
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for {
select {
// TODO: should this first chan be run as a priority channel to
@@ -105,8 +108,13 @@ func (obj *ExchangeFunc) Stream() error {
// TODO: support changing the namespace over time...
// TODO: possibly removing our stored value there first!
if obj.namespace == "" {
obj.namespace = namespace // store it
obj.watchChan = obj.init.World.StrMapWatch(obj.namespace) // watch for var changes
obj.namespace = namespace // store it
var err error
obj.watchChan, err = obj.init.World.StrMapWatch(ctx, obj.namespace) // watch for var changes
if err != nil {
return err
}
} else if obj.namespace != namespace {
return fmt.Errorf("can't change namespace, previously: `%s`", obj.namespace)
}
@@ -116,7 +124,7 @@ func (obj *ExchangeFunc) Stream() error {
obj.init.Logf("value: %+v", value)
}
if err := obj.init.World.StrMapSet(obj.namespace, value); err != nil {
if err := obj.init.World.StrMapSet(ctx, obj.namespace, value); err != nil {
return errwrap.Wrapf(err, "namespace write error of `%s` to `%s`", value, obj.namespace)
}
@@ -134,7 +142,7 @@ func (obj *ExchangeFunc) Stream() error {
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.namespace)
}
keyMap, err := obj.init.World.StrMapGet(obj.namespace)
keyMap, err := obj.init.World.StrMapGet(ctx, obj.namespace)
if err != nil {
return errwrap.Wrapf(err, "channel read failed on `%s`", obj.namespace)
}

View File

@@ -18,6 +18,7 @@
package coreworld
import (
"context"
"fmt"
"github.com/purpleidea/mgmt/lang/funcs"
@@ -73,6 +74,8 @@ func (obj *KVLookupFunc) Init(init *interfaces.Init) error {
// Stream returns the changing values that this func has over time.
func (obj *KVLookupFunc) Stream() error {
defer close(obj.init.Output) // the sender closes
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for {
select {
// TODO: should this first chan be run as a priority channel to
@@ -103,10 +106,14 @@ func (obj *KVLookupFunc) Stream() error {
// TODO: support changing the namespace over time...
// TODO: possibly removing our stored value there first!
if obj.namespace == "" {
obj.namespace = namespace // store it
obj.watchChan = obj.init.World.StrMapWatch(obj.namespace) // watch for var changes
obj.namespace = namespace // store it
var err error
obj.watchChan, err = obj.init.World.StrMapWatch(ctx, obj.namespace) // watch for var changes
if err != nil {
return err
}
result, err := obj.buildMap() // build the map...
result, err := obj.buildMap(ctx) // build the map...
if err != nil {
return err
}
@@ -135,7 +142,7 @@ func (obj *KVLookupFunc) Stream() error {
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.namespace)
}
result, err := obj.buildMap() // build the map...
result, err := obj.buildMap(ctx) // build the map...
if err != nil {
return err
}
@@ -166,8 +173,8 @@ func (obj *KVLookupFunc) Close() error {
}
// buildMap builds the result map which we'll need. It uses struct variables.
func (obj *KVLookupFunc) buildMap() (types.Value, error) {
keyMap, err := obj.init.World.StrMapGet(obj.namespace)
func (obj *KVLookupFunc) buildMap(ctx context.Context) (types.Value, error) {
keyMap, err := obj.init.World.StrMapGet(ctx, obj.namespace)
if err != nil {
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", obj.namespace)
}

View File

@@ -16,7 +16,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// test with:
// time ./mgmt run --hostname h1 --ideal-cluster-size 1 --tmp-prefix --no-pgp lang --lang examples/lang/schedule0.mcl
// time ./mgmt run --hostname h1 --tmp-prefix --no-pgp lang --lang examples/lang/schedule0.mcl
// time ./mgmt run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix --no-pgp lang --lang examples/lang/schedule0.mcl
// time ./mgmt run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp lang --lang examples/lang/schedule0.mcl
// kill h2 (should see h1 and h3 pick [h1, h3] instead)