diff --git a/etcd/etcd.go b/etcd/etcd.go index af7703bd..ffc44bf8 100644 --- a/etcd/etcd.go +++ b/etcd/etcd.go @@ -194,6 +194,7 @@ type EmbdEtcd struct { // EMBeddeD etcd advertiseClientURLs etcdtypes.URLs // client urls to advertise advertiseServerURLs etcdtypes.URLs // server urls to advertise noServer bool // disable all server peering if true + noNetwork bool // use unix:// sockets instead of TCP for clients/servers // local tracked state nominated etcdtypes.URLsMap // copy of who's nominated to locally track state @@ -220,7 +221,7 @@ type EmbdEtcd struct { // EMBeddeD etcd } // NewEmbdEtcd creates the top level embedded etcd struct client and server obj. -func NewEmbdEtcd(hostname string, seeds, clientURLs, serverURLs, advertiseClientURLs, advertiseServerURLs etcdtypes.URLs, noServer bool, idealClusterSize uint16, flags Flags, prefix string, converger converger.Converger) *EmbdEtcd { +func NewEmbdEtcd(hostname string, seeds, clientURLs, serverURLs, advertiseClientURLs, advertiseServerURLs etcdtypes.URLs, noServer bool, noNetwork bool, idealClusterSize uint16, flags Flags, prefix string, converger converger.Converger) *EmbdEtcd { endpoints := make(etcdtypes.URLsMap) if hostname == seedSentinel { // safety return nil @@ -229,6 +230,15 @@ func NewEmbdEtcd(hostname string, seeds, clientURLs, serverURLs, advertiseClient log.Printf("Etcd: need at least one seed if running with --no-server!") return nil } + if noNetwork { + if len(clientURLs) != 0 || len(serverURLs) != 0 || len(seeds) != 0 { + log.Printf("--no-network is mutual exclusive with --seeds, --client-urls and --server-urls") + return nil + } + clientURLs, _ = etcdtypes.NewURLs([]string{"unix://clients.sock:0"}) + serverURLs, _ = etcdtypes.NewURLs([]string{"unix://servers.sock:0"}) + } + if len(seeds) > 0 { endpoints[seedSentinel] = seeds idealClusterSize = 0 // unset, get from running cluster @@ -253,6 +263,7 @@ func NewEmbdEtcd(hostname string, seeds, clientURLs, serverURLs, advertiseClient advertiseClientURLs: advertiseClientURLs, advertiseServerURLs: advertiseServerURLs, noServer: noServer, + noNetwork: noNetwork, idealClusterSize: idealClusterSize, converger: converger, @@ -304,7 +315,7 @@ func (obj *EmbdEtcd) GetConfig() etcd.Config { // XXX: filter out any urls which wouldn't resolve here ? for _, eps := range obj.endpoints { // flatten map for _, u := range eps { - endpoints = append(endpoints, u.Host) // remove http:// prefix + endpoints = append(endpoints, u.String()) // use full url including scheme } } sort.Strings(endpoints) // sort for determinism @@ -1692,8 +1703,12 @@ func (obj *EmbdEtcd) LocalhostClientURLs() etcdtypes.URLs { // look through obj.clientURLs and return the localhost ones urls := etcdtypes.URLs{} for _, x := range obj.clientURLs { - // "localhost" or anything in 127.0.0.0/8 is valid! - if s := x.Host; strings.HasPrefix(s, "localhost") || strings.HasPrefix(s, "127.") { + // "localhost", ::1 or anything in 127.0.0.0/8 is valid! + if s := x.Host; strings.HasPrefix(s, "localhost") || strings.HasPrefix(s, "127.") || strings.HasPrefix(s, "[::1]") { + urls = append(urls, x) + } + // or local unix domain socket + if x.Scheme == "unix" { urls = append(urls, x) } } diff --git a/etcd/etcd_test.go b/etcd/etcd_test.go index d8b111e3..c4ef222f 100644 --- a/etcd/etcd_test.go +++ b/etcd/etcd_test.go @@ -31,7 +31,7 @@ func TestNewEmbdEtcd(t *testing.T) { noServer := false var flags Flags - obj := NewEmbdEtcd("", nil, nil, nil, nil, nil, noServer, 0, flags, "", nil) + obj := NewEmbdEtcd("", nil, nil, nil, nil, nil, noServer, false, 0, flags, "", nil) if obj == nil { t.Fatal("failed to create server object") } @@ -44,7 +44,7 @@ func TestNewEmbdEtcdConfigValidation(t *testing.T) { noServer := true var flags Flags - obj := NewEmbdEtcd("", seeds, nil, nil, nil, nil, noServer, 0, flags, "", nil) + obj := NewEmbdEtcd("", seeds, nil, nil, nil, nil, noServer, false, 0, flags, "", nil) if obj != nil { t.Fatal("server initialization should fail on invalid configuration") } diff --git a/lib/cli.go b/lib/cli.go index 96eac8a2..daf8cdfb 100644 --- a/lib/cli.go +++ b/lib/cli.go @@ -171,7 +171,11 @@ func CLI(program, version string, flags Flags) error { Name: "no-server", Usage: "do not start embedded etcd server (do not promote from client to peer)", }, - + cli.BoolFlag{ + Name: "no-network", + Usage: "run single node instance without clustering or opening tcp ports to the outside", + EnvVar: "MGMT_NO_NETWORK", + }, cli.BoolFlag{ Name: "no-pgp", Usage: "don't create pgp keys", diff --git a/lib/main.go b/lib/main.go index 951dda8a..6d596602 100644 --- a/lib/main.go +++ b/lib/main.go @@ -89,6 +89,7 @@ type Main struct { AdvertiseServerURLs []string // list of URLs to advertise for server (peer) traffic IdealClusterSize int // ideal number of server peers in cluster; only read by initial server NoServer bool // do not let other servers peer with me + NoNetwork bool // run single node instance without clustering or opening tcp ports to the outside seeds etcdtypes.URLs // processed seeds value clientURLs etcdtypes.URLs // processed client urls value @@ -354,6 +355,7 @@ func (obj *Main) Run() error { obj.advertiseClientURLs, obj.advertiseServerURLs, obj.NoServer, + obj.NoNetwork, obj.idealClusterSize, etcd.Flags{ Debug: obj.Flags.Debug, diff --git a/lib/run.go b/lib/run.go index e974930b..90130238 100644 --- a/lib/run.go +++ b/lib/run.go @@ -107,6 +107,7 @@ func run(c *cli.Context, name string, gapiObj gapi.GAPI) error { obj.AdvertiseServerURLs = cliContext.StringSlice("advertise-server-urls") obj.IdealClusterSize = cliContext.Int("ideal-cluster-size") obj.NoServer = cliContext.Bool("no-server") + obj.NoNetwork = cliContext.Bool("no-network") obj.NoPgp = cliContext.Bool("no-pgp") diff --git a/test/shell/clustersize.sh b/test/shell/clustersize.sh new file mode 100755 index 00000000..cf8d4155 --- /dev/null +++ b/test/shell/clustersize.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +if ! command -v etcdctl >/dev/null; then + echo "Missing etcdctl, skipping" + exit 0 +fi + +. "$(dirname "$0")/../util.sh" + +mkdir /tmp/mgmt/{A..E} + +# kill servers on error/exit +trap 'pkill -9 mgmt' EXIT + +"$MGMT" run --hostname h1 --tmp-prefix --no-pgp empty & +"$MGMT" run --hostname h2 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 empty & +"$MGMT" run --hostname h3 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 empty & + +# wait for everything to converge +sleep 10 + +ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 put /_mgmt/idealClusterSize 3 + +"$MGMT" run --hostname h4 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 empty & +"$MGMT" run --hostname h5 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2387 --server-urls http://127.0.0.1:2388 empty & + +# wait for everything to converge +sleep 10 + +test "$(ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 member list | wc -l)" -eq 3 + +ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2381 put /_mgmt/idealClusterSize 5 + +# wait for everything to converge +sleep 5 + +test "$(ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2381 member list | wc -l)" -eq 5 diff --git a/test/shell/exchange.sh b/test/shell/exchange.sh new file mode 100755 index 00000000..dd4a73dd --- /dev/null +++ b/test/shell/exchange.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +. "$(dirname "$0")/../util.sh" + +"$MGMT" run --hostname h1 --ideal-cluster-size 1 --tmp-prefix --no-pgp lang --lang exchange0.mcl & +"$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 exchange0.mcl & +"$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 exchange0.mcl & +"$MGMT" run --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 --tmp-prefix --no-pgp lang --lang exchange0.mcl & + +# kill servers on error/exit +trap 'pkill -9 mgmt' EXIT + +# wait for everything to converge +sleep 10 + +test "$(cat /tmp/mgmt/exchange-* | grep -c h1)" -eq 4 +test "$(cat /tmp/mgmt/exchange-* | grep -c h2)" -eq 4 +test "$(cat /tmp/mgmt/exchange-* | grep -c h3)" -eq 4 +test "$(cat /tmp/mgmt/exchange-* | grep -c h4)" -eq 4 diff --git a/test/shell/exchange0.mcl b/test/shell/exchange0.mcl new file mode 100644 index 00000000..a6d1f3bf --- /dev/null +++ b/test/shell/exchange0.mcl @@ -0,0 +1,16 @@ +# run this example with these commands +# watch -n 0.1 'tail *' # run this in /tmp/mgmt/ +# time ./mgmt run --hostname h1 --ideal-cluster-size 1 --tmp-prefix --no-pgp lang --lang examples/lang/exchange0.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/exchange0.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/exchange0.mcl +# time ./mgmt run --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 --tmp-prefix --no-pgp lang --lang examples/lang/exchange0.mcl + +import "sys" +import "world" + +$rand = random1(8) +$exchanged = world.exchange("keyns", $rand) + +file "/tmp/mgmt/exchange-${sys.hostname()}" { + content => template("Found: {{ . }}\n", $exchanged), +} diff --git a/test/shell/ipv6-localhost.sh b/test/shell/ipv6-localhost.sh new file mode 100755 index 00000000..4afadd0d --- /dev/null +++ b/test/shell/ipv6-localhost.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +if ! ifconfig lo | grep 'inet6 ::1' >/dev/null; then + echo "No IPv6, skipping test" + exit 0 +fi + +. "$(dirname "$0")/../util.sh" + +tmpdir="$($mktemp --tmpdir -d tmp.XXX)" + +# run empty graph listing only to IPv6 addresses +"$MGMT" run --client-urls "http://[::1]:2379" --server-urls "http://[::1]:2380" --tmp-prefix empty & +pid=$! + +# kill server on error/exit +trap 'pkill -9 mgmt' EXIT + +# give mgmt a little time to startup +sleep 10 + +# mgmt configured for ipv6 only should not listen on any IPv4 ports +lsof -Pn -p "$pid" -a -i | grep '127.0.0.1' && false + +# instead it should listen on IPv6 +lsof -Pn -p "$pid" -a -i | grep '::1' || false diff --git a/test/shell/no-network.sh b/test/shell/no-network.sh new file mode 100755 index 00000000..180d7be7 --- /dev/null +++ b/test/shell/no-network.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Tests the behaviour of the --no-network +set -o errexit +set -o pipefail + +. "$(dirname "$0")/../util.sh" + +tmpdir="$($mktemp --tmpdir -d tmp.XXX)" + +# run empty graph, with standalone enabled +"$MGMT" run --no-network --prefix "$tmpdir" empty & +pid=$! + +# kill server on error/exit +trap 'kill -SIGINT "$pid"' EXIT + +# give mgmt a little time to startup +sleep 10 + +# standalone mgmt should not listen on any tcp ports +lsof -i | grep "$pid" | grep TCP && false + +# instead unix domain sockets should have been created +test -S "servers.sock:0" +test -S "clients.sock:0" diff --git a/test/test-gotest.sh b/test/test-gotest.sh index ebfa7618..916a75ac 100755 --- a/test/test-gotest.sh +++ b/test/test-gotest.sh @@ -21,7 +21,7 @@ fi # As per https://github.com/travis-ci/docs-travis-ci-com/blob/master/user/docker.md # Docker is not supported on Travis macOS test instances. -if [[ "$TRAVIS_OS_NAME" == "osx" ]];then +if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then XTAGS+=('nodocker') fi