diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml index 338180cd1..33379e805 100644 --- a/.gitlab/.gitlab-ci.yml +++ b/.gitlab/.gitlab-ci.yml @@ -138,6 +138,24 @@ run go tests future: tags: - aws +run go tests future plg: + stage: test + allow_failure: true + image: golang:1.18 + rules: + - if: '$CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"' + retry: 1 + script: + - echo "Running featurebase plg-specific unit tests..." + - PKG_LIST=$(go list ./... | grep -v internal/clustertests | paste -s -d, -) + - go test -tags=plg -timeout=30m -json -coverprofile=coverage-plg.out -covermode=atomic -coverpkg=${PKG_LIST} ./... | tee test-report-plg.out + artifacts: + paths: + - coverage-plg.out + - test-report-plg.out + tags: + - aws + upload to sonarcloud: stage: integration image: sonarsource/sonar-scanner-cli:4.6 @@ -146,7 +164,7 @@ upload to sonarcloud: rules: - if: '$CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_SOURCE == "web"' script: - - sonar-scanner -Dsonar.projectKey=molecula_featurebase -Dsonar.organization=molecula -Dsonar.sources=. -Dsonar.host.url=https://sonarcloud.io -Dsonar.go.coverage.reportPaths=coverage.out,results/coverage*.out -Dsonar.go.tests.reportPaths=test-report.out,results/report* -Dsonar.javascript.lcov.reportPaths=lattice/coverage/lcov.info + - sonar-scanner -Dsonar.projectKey=molecula_featurebase -Dsonar.organization=molecula -Dsonar.sources=. -Dsonar.host.url=https://sonarcloud.io -Dsonar.go.coverage.reportPaths=coverage-plg.out,results/coverage*.out -Dsonar.go.tests.reportPaths=test-report-plg.out,results/report* -Dsonar.javascript.lcov.reportPaths=lattice/coverage/lcov.info needs: - job: run go tests future - job: run jest tests diff --git a/Makefile b/Makefile index a3eb1948a..18f4b7df8 100644 --- a/Makefile +++ b/Makefile @@ -168,6 +168,10 @@ authclustertests: vendor install: $(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/featurebase +# Install the single-node PLG version of FeatureBase +plg: + $(GO) install -tags='plg $(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/featurebase + install-bench: $(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa-bench diff --git a/etcd/embed.go b/etcd/embed.go index 17d567dac..e2d2ee712 100644 --- a/etcd/embed.go +++ b/etcd/embed.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "net" + "os" "path" "sort" "strings" @@ -16,6 +17,7 @@ import ( "github.com/molecula/featurebase/v3/disco" "github.com/molecula/featurebase/v3/logger" + "github.com/molecula/featurebase/v3/monitor" "github.com/molecula/featurebase/v3/topology" "github.com/pkg/errors" "go.etcd.io/etcd/api/v3/mvccpb" @@ -257,36 +259,48 @@ func (e *Etcd) retryClient(fn func(cli *clientv3.Client) error) (err error) { return errors.Wrap(err, "exhausted all retries") } -func parseOptions(opt Options) *embed.Config { +func (e *Etcd) parseOptions() *embed.Config { cfg := embed.NewConfig() cfg.LogLevel = "error" cfg.Logger = "zap" - cfg.Name = opt.Name - cfg.Dir = opt.Dir - cfg.InitialClusterToken = opt.ClusterName - cfg.LCUrls = types.MustNewURLs([]string{opt.LClientURL}) - cfg.UnsafeNoFsync = opt.UnsafeNoFsync - if opt.AClientURL != "" { - cfg.ACUrls = types.MustNewURLs([]string{opt.AClientURL}) + cfg.Name = e.options.Name + cfg.Dir = e.options.Dir + cfg.InitialClusterToken = e.options.ClusterName + cfg.LCUrls = types.MustNewURLs([]string{e.options.LClientURL}) + cfg.UnsafeNoFsync = e.options.UnsafeNoFsync + if e.options.AClientURL != "" { + cfg.ACUrls = types.MustNewURLs([]string{e.options.AClientURL}) } else { cfg.ACUrls = cfg.LCUrls } - cfg.LPUrls = types.MustNewURLs([]string{opt.LPeerURL}) - if opt.APeerURL != "" { - cfg.APUrls = types.MustNewURLs([]string{opt.APeerURL}) + cfg.LPUrls = types.MustNewURLs([]string{e.options.LPeerURL}) + if e.options.APeerURL != "" { + cfg.APUrls = types.MustNewURLs([]string{e.options.APeerURL}) } else { cfg.APUrls = cfg.LPUrls } - if opt.InitCluster != "" { - cfg.InitialCluster = opt.InitCluster + if e.options.InitCluster != "" { + // Checks if FB is running the single-node free version or the multi-node + // enterprise version. Sentry.io is enabled on single-node. + if AllowCluster() == false { + monitor.InitErrorMonitor() + e.logger.Infof("Initializing Monitor: Capturing usage metrics") + //check for multiple nodes in the cluster and error if present + nodes := strings.Split(e.options.InitCluster, ",") + if len(nodes) > 1 { + e.logger.Warnf("Multiple cluster nodes detected - this version of FeatureBase only supports single node. %+v", e.options.InitCluster) + os.Exit(1) + } + } + cfg.InitialCluster = e.options.InitCluster cfg.ClusterState = embed.ClusterStateFlagNew } else { - cfg.InitialCluster = cfg.Name + "=" + opt.APeerURL + cfg.InitialCluster = cfg.Name + "=" + e.options.APeerURL } - if opt.ClusterURL != "" { + if e.options.ClusterURL != "" { cfg.ClusterState = embed.ClusterStateFlagExisting - cli, err := clientv3.NewFromURL(opt.ClusterURL) + cli, err := clientv3.NewFromURL(e.options.ClusterURL) if err != nil { panic(err) } @@ -294,25 +308,26 @@ func parseOptions(opt Options) *embed.Config { log.Println("Cluster Members:") mIDs, mNames, mURLs := memberList(cli) + for i, id := range mIDs { log.Printf("\tid: %d, name: %s, url: %s\n", id, mNames[i], mURLs[i]) cfg.InitialCluster += "," + mNames[i] + "=" + mURLs[i] } log.Println("Joining Cluster:") - id, name := memberAdd(cli, opt.APeerURL) + id, name := memberAdd(cli, e.options.APeerURL) log.Printf("\tid: %d, name: %s\n", id, name) } // can only use tls if not using pre-configured listeners cfg.ClientTLSInfo = transport.TLSInfo{ - TrustedCAFile: opt.TrustedCAFile, - CertFile: opt.ClientCertFile, - KeyFile: opt.ClientKeyFile, + TrustedCAFile: e.options.TrustedCAFile, + CertFile: e.options.ClientCertFile, + KeyFile: e.options.ClientKeyFile, } cfg.PeerTLSInfo = transport.TLSInfo{ - TrustedCAFile: opt.TrustedCAFile, - CertFile: opt.PeerCertFile, - KeyFile: opt.PeerKeyFile, + TrustedCAFile: e.options.TrustedCAFile, + CertFile: e.options.PeerCertFile, + KeyFile: e.options.PeerKeyFile, } return cfg @@ -320,7 +335,7 @@ func parseOptions(opt Options) *embed.Config { // Start starts etcd and hearbeat func (e *Etcd) Start(ctx context.Context) (_ disco.InitialClusterState, err error) { - opts := parseOptions(e.options) + opts := e.parseOptions() state := disco.InitialClusterState(opts.ClusterState) e.e, err = embed.StartEtcd(opts) diff --git a/etcd/enterprise_cluster.go b/etcd/enterprise_cluster.go new file mode 100644 index 000000000..4f00c3b4c --- /dev/null +++ b/etcd/enterprise_cluster.go @@ -0,0 +1,11 @@ +//go:build !plg +// +build !plg + +// Copyright 2022 Molecula Corp. All rights reserved. +package etcd + +// allows us to conditionally build an enterprise version of +// FB that does cluster, in contrast to the PLG version. +func AllowCluster() bool { + return true +} diff --git a/etcd/plg_cluster.go b/etcd/plg_cluster.go new file mode 100644 index 000000000..300db2984 --- /dev/null +++ b/etcd/plg_cluster.go @@ -0,0 +1,11 @@ +//go:build plg +// +build plg + +// Copyright 2022 Molecula Corp. All rights reserved. +package etcd + +// allows us to conditionally build a version of FB that does not +// cluster, to make a demo available for our Product Led Growth. +func AllowCluster() bool { + return false +}