Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aerospike storage backend #10131

Merged
merged 8 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
logicalDb "github.com/hashicorp/vault/builtin/logical/database"

physAerospike "github.com/hashicorp/vault/physical/aerospike"
physAliCloudOSS "github.com/hashicorp/vault/physical/alicloudoss"
physAzure "github.com/hashicorp/vault/physical/azure"
physCassandra "github.com/hashicorp/vault/physical/cassandra"
Expand Down Expand Up @@ -132,6 +133,7 @@ var (
}

physicalBackends = map[string]physical.Factory{
"aerospike": physAerospike.NewAerospikeBackend,
"alicloudoss": physAliCloudOSS.NewAliCloudOSSBackend,
"azure": physAzure.NewAzureBackend,
"cassandra": physCassandra.NewCassandraBackend,
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/SAP/go-hdb v0.14.1
github.com/Sectorbob/mlab-ns2 v0.0.0-20171030222938-d3aa0c295a8a
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/aerospike/aerospike-client-go v3.1.0+incompatible
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit curious as to why this particular pseudo-version was chosen since v3.1.1 is available.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, the +incompatible label is actually how go/mod sees the release: https://pkg.go.dev/github.com/aerospike/[email protected]+incompatible?tab=versions

Since v3.1.1+incompatible has been released, we should consider bumping it to that version (unless there's a particular reason not to)

github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5
github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2
Expand Down Expand Up @@ -143,6 +144,7 @@ require (
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
github.com/xdg/stringprep v1.0.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da // indirect
go.etcd.io/bbolt v1.3.5
go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547
go.mongodb.org/mongo-driver v1.2.1
Expand Down
10 changes: 5 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
Expand All @@ -112,6 +111,8 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUW
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/aerospike/aerospike-client-go v3.1.0+incompatible h1:ggcqXpZOCBlMptXPooX9MQfOa8aKIPhdCqLwAjR5M9U=
github.com/aerospike/aerospike-client-go v3.1.0+incompatible/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
Expand Down Expand Up @@ -536,10 +537,6 @@ github.com/hashicorp/vault-plugin-auth-kubernetes v0.7.1-0.20200921171209-a8c355
github.com/hashicorp/vault-plugin-auth-kubernetes v0.7.1-0.20200921171209-a8c355e565cb/go.mod h1:2c/k3nsoGPKV+zpAWCiajt4e66vncEq8Li/eKLqErAc=
github.com/hashicorp/vault-plugin-auth-oci v0.5.5 h1:nIP8g+VZd2V+LY/D5omWhLSnhHuogIJx7Bz6JyLt628=
github.com/hashicorp/vault-plugin-auth-oci v0.5.5/go.mod h1:Cn5cjR279Y+snw8LTaiLTko3KGrbigRbsQPOd2D5xDw=
github.com/hashicorp/vault-plugin-database-couchbase v0.1.0 h1:P/ji+KVmIXDyF3dM2PVb5wUpNMeEieFqJpj9derJlPg=
github.com/hashicorp/vault-plugin-database-couchbase v0.1.0/go.mod h1:N4esW48+x1CClz6unRkGZGUBBR87iMMLbpHpnkQDiXg=
github.com/hashicorp/vault-plugin-database-couchbase v0.1.1-0.20201006201549-a6eff27be5be h1:QypMebL8zdVtZJmJ0LmIdEcZAarLLNGfWoA4ATMRO5I=
github.com/hashicorp/vault-plugin-database-couchbase v0.1.1-0.20201006201549-a6eff27be5be/go.mod h1:7bQzTBzTwG0x+E9+MjEVbqBSlN524VvebPOsKJTysXM=
github.com/hashicorp/vault-plugin-database-couchbase v0.1.1-0.20201009184913-83c385bec4a2 h1:OtSJIcQjq7VFpIbbY2mT7oy1CAeIdeDB1sTitwikimI=
github.com/hashicorp/vault-plugin-database-couchbase v0.1.1-0.20201009184913-83c385bec4a2/go.mod h1:UsqbPpxKodWyNmiEVkL1UkzTUkd33llDcQ34ElAEsuc=
github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4 h1:YE4qndazWmYGpVOoZI7nDGG+gwTZKzL1Ou4WZQ+Tdxk=
Expand Down Expand Up @@ -919,6 +916,8 @@ github.com/yandex-cloud/go-genproto v0.0.0-20200722140432-762fe965ce77/go.mod h1
github.com/yandex-cloud/go-sdk v0.0.0-20200722140627-2194e5077f13/go.mod h1:LEdAMqa1v/7KYe4b13ALLkonuDxLph57ibUb50ctvJk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
Expand Down Expand Up @@ -1063,6 +1062,7 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
155 changes: 155 additions & 0 deletions physical/aerospike/aerospike.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package aerospike

import (
"context"
"fmt"
"hash/fnv"
"strconv"
"strings"

aero "github.com/aerospike/aerospike-client-go"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/physical"
)

const (
keyBin = "keyBin"
valueBin = "valueBin"
)

// AerospikeBackend is a physical backend that stores data in Aerospike.
type AerospikeBackend struct {
client *aero.Client
namespace string
set string
logger log.Logger
}

// Verify AerospikeBackend satisfies the correct interface.
var _ physical.Backend = (*AerospikeBackend)(nil)

// NewAerospikeBackend constructs an AerospikeBackend backend.
func NewAerospikeBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
namespace := conf["namespace"]
set := conf["set"]

policy := aero.NewClientPolicy()
policy.User = conf["username"]
policy.Password = conf["password"]

port, err := strconv.Atoi(conf["port"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If port is not provided, the error returned does not make it obvious that this is the case. Consider checking the value beforehand separately.

strconv.Atoi: parsing "": invalid syntax

if err != nil {
return nil, err
}

client, err := aero.NewClientWithPolicy(policy, conf["hostname"], port)
if err != nil {
return nil, err
}

return &AerospikeBackend{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since (per the docs), set/namespace/hostname are all required, should we be validating != "" before successfully returning?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like set is optional, and both namespace and hostname now have default values so we should be good.

client: client,
namespace: namespace,
set: set,
logger: logger,
}, nil
}

func (a *AerospikeBackend) key(userKey string) (*aero.Key, error) {
return aero.NewKey(a.namespace, a.set, hash(userKey))
}

// Put is used to insert or update an entry.
func (a *AerospikeBackend) Put(ctx context.Context, entry *physical.Entry) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/ctx/_ on these unused ctx parameters

aeroKey, err := a.key(entry.Key)
if err != nil {
return err
}

writePolicy := aero.NewWritePolicy(0, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I comment or docs link that has some details about this would be helpful.

writePolicy.RecordExistsAction = aero.REPLACE

binMap := make(aero.BinMap, 2)
binMap[keyBin] = entry.Key
binMap[valueBin] = entry.Value

return a.client.Put(writePolicy, aeroKey, binMap)
}

// Get is used to fetch an entry.
func (a *AerospikeBackend) Get(ctx context.Context, key string) (*physical.Entry, error) {
aeroKey, err := a.key(key)
if err != nil {
return nil, err
}

record, err := a.client.Get(nil, aeroKey)
if err != nil {
if err.Error() == "Key not found" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the compared-to error exported by chance?

return nil, nil
}
return nil, err
}

return &physical.Entry{
Key: key,
Value: record.Bins[valueBin].([]byte),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering indexing against valueBin in advance so we can check for the presence of the key and error (otherwise we'll panic on nil)

}, nil
}

// Delete is used to permanently delete an entry.
func (a *AerospikeBackend) Delete(ctx context.Context, key string) error {
aeroKey, err := a.key(key)
if err != nil {
return err
}

_, err = a.client.Delete(nil, aeroKey)
return err
}

// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (a *AerospikeBackend) List(ctx context.Context, prefix string) ([]string, error) {
recordSet, err := a.client.ScanAll(nil, a.namespace, a.set)
calvn marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

var keyList []string
for res := range recordSet.Results() {
if res.Err != nil {
return nil, res.Err
}
recordKey := res.Record.Bins[keyBin].(string)
if strings.HasPrefix(recordKey, prefix) {
trimPrefix := strings.Replace(recordKey, prefix, "", 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings.TrimPrefix is a little simpler.

keys := strings.Split(trimPrefix, "/")
if len(keys) == 1 {
keyList = append(keyList, keys[0])
} else {
withSlash := keys[0] + "/"
if !listContains(keyList, withSlash) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a helper you can use:

func StrListContains(haystack []string, needle string) bool {

keyList = append(keyList, withSlash)
}
}
}
}

return keyList, nil
}

func hash(s string) string {
h := fnv.New32a()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this was chosen in particular? 32-bits is a fairly small hash size. We'd typically use sha256 to satisfy both irreversibility and collision avoidance.

h.Write([]byte(s))
return fmt.Sprint(h.Sum32())
}

func listContains(list []string, s string) bool {
for _, i := range list {
if i == s {
return true
}
}
return false
}
88 changes: 88 additions & 0 deletions physical/aerospike/aerospike_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package aerospike

import (
"context"
"testing"
"time"

aero "github.com/aerospike/aerospike-client-go"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/testhelpers/docker"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/physical"
)

func TestAerospikeBackend(t *testing.T) {
cleanup, config := prepareAerospikeContainer(t)
defer cleanup()

logger := logging.NewVaultLogger(log.Debug)

b, err := NewAerospikeBackend(map[string]string{
"hostname": config.hostname,
"port": config.port,
"namespace": config.namespace,
"set": config.set,
}, logger)

if err != nil {
t.Fatalf("err: %s", err)
}

physical.ExerciseBackend(t, b)
physical.ExerciseBackend_ListPrefix(t, b)
}

type aerospikeConfig struct {
hostname string
port string
namespace string
set string
}

func prepareAerospikeContainer(t *testing.T) (func(), *aerospikeConfig) {
runner, err := docker.NewServiceRunner(docker.RunOptions{
ImageRepo: "aerospike/aerospike-server",
ContainerName: "aerospikedb",
ImageTag: "latest",
Ports: []string{"3000/tcp", "3001/tcp", "3002/tcp", "3003/tcp"},
DoNotAutoRemove: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this strictly necessary? This leaves dangling containers on the host, and may result in errors if tests are ran multiple times due to colliding container name.

})
if err != nil {
t.Fatalf("Could not start local Aerospike: %s", err)
}

svc, err := runner.StartService(context.Background(),
func(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
cfg := docker.NewServiceHostPort(host, port)

time.Sleep(time.Second)
client, err := aero.NewClient(host, port)
if err != nil {
return nil, err
}

node, err := client.Cluster().GetRandomNode()
if err != nil {
return nil, err
}

_, err = node.RequestInfo(aero.NewInfoPolicy(), "namespaces")
if err != nil {
return nil, err
}

return cfg, nil
},
)
if err != nil {
t.Fatalf("Could not start local Aerospike: %s", err)
}

return svc.Cleanup, &aerospikeConfig{
hostname: svc.Config.URL().Hostname(),
port: svc.Config.URL().Port(),
namespace: "test",
set: "vault",
}
}
15 changes: 15 additions & 0 deletions vendor/github.com/aerospike/aerospike-client-go/.build.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions vendor/github.com/aerospike/aerospike-client-go/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading