mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-08 16:24:51 -04:00
Raft Storage Backend (#6888)
* Work on raft backend * Add logstore locally * Add encryptor and unsealable interfaces * Add clustering support to raft * Remove client and handler * Bootstrap raft on init * Cleanup raft logic a bit * More raft work * Work on TLS config * More work on bootstrapping * Fix build * More work on bootstrapping * More bootstrapping work * fix build * Remove consul dep * Fix build * merged oss/master into raft-storage * Work on bootstrapping * Get bootstrapping to work * Clean up FMS and node-id * Update local node ID logic * Cleanup node-id change * Work on snapshotting * Raft: Add remove peer API (#906) * Add remove peer API * Add some comments * Fix existing snapshotting (#909) * Raft get peers API (#912) * Read raft configuration * address review feedback * Use the Leadership Transfer API to step-down the active node (#918) * Raft join and unseal using Shamir keys (#917) * Raft join using shamir * Store AEAD instead of master key * Split the raft join process to answer the challenge after a successful unseal * get the follower to standby state * Make unseal work * minor changes * Some input checks * reuse the shamir seal access instead of new default seal access * refactor joinRaftSendAnswer function * Synchronously send answer in auto-unseal case * Address review feedback * Raft snapshots (#910) * Fix existing snapshotting * implement the noop snapshotting * Add comments and switch log libraries * add some snapshot tests * add snapshot test file * add TODO * More work on raft snapshotting * progress on the ConfigStore strategy * Don't use two buckets * Update the snapshot store logic to hide the file logic * Add more backend tests * Cleanup code a bit * [WIP] Raft recovery (#938) * Add recovery functionality * remove fmt.Printfs * Fix a few fsm bugs * Add max size value for raft backend (#942) * Add max size value for raft backend * Include physical.ErrValueTooLarge in the message * Raft snapshot Take/Restore API (#926) * Inital work on raft snapshot APIs * Always redirect snapshot install/download requests * More work on the snapshot APIs * Cleanup code a bit * On restore handle special cases * Use the seal to encrypt the sha sum file * Add sealer mechanism and fix some bugs * Call restore while state lock is held * Send restore cb trigger through raft log * Make error messages nicer * Add test helpers * Add snapshot test * Add shamir unseal test * Add more raft snapshot API tests * Fix locking * Change working to initalize * Add underlying raw object to test cluster core * Move leaderUUID to core * Add raft TLS rotation logic (#950) * Add TLS rotation logic * Cleanup logic a bit * Add/Remove from follower state on add/remove peer * add comments * Update more comments * Update request_forwarding_service.proto * Make sure we populate all nodes in the followerstate obj * Update times * Apply review feedback * Add more raft config setting (#947) * Add performance config setting * Add more config options and fix tests * Test Raft Recovery (#944) * Test raft recovery * Leave out a node during recovery * remove unused struct * Update physical/raft/snapshot_test.go * Update physical/raft/snapshot_test.go * fix vendoring * Switch to new raft interface * Remove unused files * Switch a gogo -> proto instance * Remove unneeded vault dep in go.sum * Update helper/testhelpers/testhelpers.go Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * Update vault/cluster/cluster.go * track active key within the keyring itself (#6915) * track active key within the keyring itself * lookup and store using the active key ID * update docstring * minor refactor * Small text fixes (#6912) * Update physical/raft/raft.go Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * review feedback * Move raft logical system into separate file * Update help text a bit * Enforce cluster addr is set and use it for raft bootstrapping * Fix tests * fix http test panic * Pull in latest raft-snapshot library * Add comment
This commit is contained in:
parent
11e0ec8bf5
commit
b435028f3f
269 changed files with 51967 additions and 4718 deletions
4
Makefile
4
Makefile
|
|
@ -178,11 +178,13 @@ proto:
|
|||
protoc helper/forwarding/types.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc sdk/logical/*.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc sdk/physical/types.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc physical/raft/types.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc helper/identity/mfa/types.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc helper/identity/types.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc sdk/database/dbplugin/*.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
protoc sdk/plugin/pb/*.proto --go_out=plugins=grpc,paths=source_relative:.
|
||||
sed -i -e 's/Idp/IDP/' -e 's/Url/URL/' -e 's/Id/ID/' -e 's/IDentity/Identity/' -e 's/EntityId/EntityID/' -e 's/Api/API/' -e 's/Qr/QR/' -e 's/Totp/TOTP/' -e 's/Mfa/MFA/' -e 's/Pingid/PingID/' -e 's/protobuf:"/sentinel:"" protobuf:"/' -e 's/namespaceId/namespaceID/' -e 's/Ttl/TTL/' -e 's/BoundCidrs/BoundCIDRs/' helper/identity/types.pb.go helper/identity/mfa/types.pb.go helper/storagepacker/types.pb.go sdk/plugin/pb/backend.pb.go sdk/logical/identity.pb.go
|
||||
sed -i -e 's/Id/ID/' vault/request_forwarding_service.pb.go
|
||||
sed -i -e 's/Idp/IDP/' -e 's/Url/URL/' -e 's/Id/ID/' -e 's/IDentity/Identity/' -e 's/EntityId/EntityID/' -e 's/Api/API/' -e 's/Qr/QR/' -e 's/Totp/TOTP/' -e 's/Mfa/MFA/' -e 's/Pingid/PingID/' -e 's/protobuf:"/sentinel:"" protobuf:"/' -e 's/namespaceId/namespaceID/' -e 's/Ttl/TTL/' -e 's/BoundCidrs/BoundCIDRs/' helper/identity/types.pb.go helper/identity/mfa/types.pb.go helper/storagepacker/types.pb.go sdk/plugin/pb/backend.pb.go sdk/logical/identity.pb.go
|
||||
sed -i -e 's/Iv/IV/' -e 's/Hmac/HMAC/' sdk/physical/types.pb.go
|
||||
|
||||
fmtcheck:
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import (
|
|||
physMSSQL "github.com/hashicorp/vault/physical/mssql"
|
||||
physMySQL "github.com/hashicorp/vault/physical/mysql"
|
||||
physPostgreSQL "github.com/hashicorp/vault/physical/postgresql"
|
||||
physRaft "github.com/hashicorp/vault/physical/raft"
|
||||
physS3 "github.com/hashicorp/vault/physical/s3"
|
||||
physSpanner "github.com/hashicorp/vault/physical/spanner"
|
||||
physSwift "github.com/hashicorp/vault/physical/swift"
|
||||
|
|
@ -145,6 +146,7 @@ var (
|
|||
"s3": physS3.NewS3Backend,
|
||||
"spanner": physSpanner.NewBackend,
|
||||
"swift": physSwift.NewSwiftBackend,
|
||||
"raft": physRaft.NewRaftBackend,
|
||||
"zookeeper": physZooKeeper.NewZooKeeperBackend,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/hashicorp/vault/shamir"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
)
|
||||
|
||||
func TestSealMigration(t *testing.T) {
|
||||
|
|
@ -28,7 +29,7 @@ func TestSealMigration(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
shamirSeal := vault.NewDefaultSeal()
|
||||
shamirSeal := vault.NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("shamir")))
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Seal: shamirSeal,
|
||||
Physical: phys,
|
||||
|
|
@ -113,7 +114,7 @@ func TestSealMigration(t *testing.T) {
|
|||
newSeal := vault.NewAutoSeal(seal.NewTestSeal(nil))
|
||||
newSeal.SetCore(core)
|
||||
autoSeal = newSeal
|
||||
if err := adjustCoreForSealMigration(core, newSeal, nil); err != nil {
|
||||
if err := adjustCoreForSealMigration(logger, core, newSeal, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +211,7 @@ func TestSealMigration(t *testing.T) {
|
|||
|
||||
core := cluster.Cores[0].Core
|
||||
|
||||
if err := adjustCoreForSealMigration(core, altSeal, autoSeal); err != nil {
|
||||
if err := adjustCoreForSealMigration(logger, core, altSeal, autoSeal); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +249,7 @@ func TestSealMigration(t *testing.T) {
|
|||
|
||||
core := cluster.Cores[0].Core
|
||||
|
||||
if err := adjustCoreForSealMigration(core, shamirSeal, altSeal); err != nil {
|
||||
if err := adjustCoreForSealMigration(logger, core, shamirSeal, altSeal); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import (
|
|||
"github.com/hashicorp/vault/sdk/version"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
vaultseal "github.com/hashicorp/vault/vault/seal"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
"github.com/mitchellh/cli"
|
||||
testing "github.com/mitchellh/go-testing-interface"
|
||||
"github.com/posener/complete"
|
||||
|
|
@ -493,6 +494,10 @@ func (c *ServerCommand) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("Unknown storage type %s", config.Storage.Type))
|
||||
return 1
|
||||
}
|
||||
if config.Storage.Type == "raft" && len(config.ClusterAddr) == 0 {
|
||||
c.UI.Error("Cluster address must be set when using raft storage")
|
||||
return 1
|
||||
}
|
||||
namedStorageLogger := c.logger.Named("storage." + config.Storage.Type)
|
||||
allLoggers = append(allLoggers, namedStorageLogger)
|
||||
backend, err := factory(config.Storage.Config, namedStorageLogger)
|
||||
|
|
@ -541,7 +546,7 @@ func (c *ServerCommand) Run(args []string) int {
|
|||
var seal vault.Seal
|
||||
sealLogger := c.logger.Named(sealType)
|
||||
allLoggers = append(allLoggers, sealLogger)
|
||||
seal, sealConfigError = serverseal.ConfigureSeal(configSeal, &infoKeys, &info, sealLogger, vault.NewDefaultSeal())
|
||||
seal, sealConfigError = serverseal.ConfigureSeal(configSeal, &infoKeys, &info, sealLogger, vault.NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("shamir"))))
|
||||
if sealConfigError != nil {
|
||||
if !errwrap.ContainsType(sealConfigError, new(logical.KeyNotFoundError)) {
|
||||
c.UI.Error(fmt.Sprintf(
|
||||
|
|
@ -972,7 +977,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
|
|||
}))
|
||||
|
||||
// Before unsealing with stored keys, setup seal migration if needed
|
||||
if err := adjustCoreForSealMigration(core, barrierSeal, unwrapSeal); err != nil {
|
||||
if err := adjustCoreForSealMigration(c.logger, core, barrierSeal, unwrapSeal); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/version"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
testing "github.com/mitchellh/go-testing-interface"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
|
@ -85,7 +86,7 @@ func (c *ServerCommand) enableFourClusterDev(base *vault.CoreConfig, info map[st
|
|||
return errors.New("")
|
||||
}
|
||||
base.Physical = backend
|
||||
base.Seal = vault.NewDefaultSeal()
|
||||
base.Seal = vault.NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("shamir")))
|
||||
|
||||
testCluster := vault.NewTestCluster(&testing.RuntimeT{}, base, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
vaultseal "github.com/hashicorp/vault/vault/seal"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ var (
|
|||
onEnterprise = false
|
||||
)
|
||||
|
||||
func adjustCoreForSealMigration(core *vault.Core, barrierSeal, unwrapSeal vault.Seal) error {
|
||||
func adjustCoreForSealMigration(logger log.Logger, core *vault.Core, barrierSeal, unwrapSeal vault.Seal) error {
|
||||
existBarrierSealConfig, existRecoverySealConfig, err := core.PhysicalSealConfigs(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error checking for existing seal: %s", err)
|
||||
|
|
@ -61,7 +63,7 @@ func adjustCoreForSealMigration(core *vault.Core, barrierSeal, unwrapSeal vault.
|
|||
switch existBarrierSealConfig.Type {
|
||||
case vaultseal.Shamir:
|
||||
// The value reflected in config is what we're going to
|
||||
existSeal = vault.NewDefaultSeal()
|
||||
existSeal = vault.NewDefaultSeal(shamirseal.NewSeal(logger.Named("shamir")))
|
||||
newSeal = barrierSeal
|
||||
newBarrierSealConfig := &vault.SealConfig{
|
||||
Type: newSeal.BarrierType(),
|
||||
|
|
|
|||
11
go.mod
11
go.mod
|
|
@ -11,7 +11,6 @@ require (
|
|||
github.com/Azure/azure-sdk-for-go v27.1.0+incompatible
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Azure/go-autorest v11.7.1+incompatible
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible // indirect
|
||||
github.com/Microsoft/go-winio v0.4.12 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
|
|
@ -20,7 +19,7 @@ require (
|
|||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190412020505-60e2075261b6
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5
|
||||
github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878
|
||||
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
|
||||
|
|
@ -31,8 +30,6 @@ require (
|
|||
github.com/boombuler/barcode v1.0.0 // indirect
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||
github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0
|
||||
github.com/circonus-labs/circonus-gometrics v2.2.7+incompatible // indirect
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
|
||||
github.com/cockroachdb/apd v1.1.0 // indirect
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||
|
|
@ -50,6 +47,7 @@ require (
|
|||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31
|
||||
github.com/gocql/gocql v0.0.0-20190402132108-0e1d5de854df
|
||||
github.com/gogo/protobuf v1.2.1
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/google/go-github v17.0.0+incompatible
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
|
|
@ -60,6 +58,7 @@ require (
|
|||
github.com/hashicorp/go-gcp-common v0.5.0
|
||||
github.com/hashicorp/go-hclog v0.9.2
|
||||
github.com/hashicorp/go-memdb v1.0.0
|
||||
github.com/hashicorp/go-msgpack v0.5.5
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/hashicorp/go-rootcerts v1.0.0
|
||||
github.com/hashicorp/go-sockaddr v1.0.2
|
||||
|
|
@ -68,6 +67,8 @@ require (
|
|||
github.com/hashicorp/golang-lru v0.5.1
|
||||
github.com/hashicorp/hcl v1.0.0
|
||||
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf
|
||||
github.com/hashicorp/raft v1.0.2-0.20190617182316-3db06beda834
|
||||
github.com/hashicorp/raft-snapshot v1.0.1
|
||||
github.com/hashicorp/vault-plugin-auth-alicloud v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-azure v0.5.1
|
||||
github.com/hashicorp/vault-plugin-auth-centrify v0.5.1
|
||||
|
|
@ -120,7 +121,7 @@ require (
|
|||
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||
go.etcd.io/bbolt v1.3.2
|
||||
go.etcd.io/etcd v0.0.0-20190412021913-f29b1ada1971
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
||||
|
|
|
|||
29
go.sum
29
go.sum
|
|
@ -44,6 +44,8 @@ github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2/go.
|
|||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e h1:h0gP0hBU6DsA5IQduhLWGOEfIUKzJS5hhXQBSgHuF/g=
|
||||
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
|
|
@ -63,6 +65,7 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYE
|
|||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f h1:ZMEzE7R0WNqgbHplzSBaYJhJi5AZWTCK9baU0ebzG6g=
|
||||
|
|
@ -75,8 +78,8 @@ github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f h1:gJzxr
|
|||
github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f/go.mod h1:C0rtzmGXgN78pYR0tGJFhtHgkbAs0lIbHwkB81VxDQE=
|
||||
github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0 h1:CWU8piLyqoi9qXEUwzOh5KFKGgmSU5ZhktJyYcq6ryQ=
|
||||
github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0/go.mod h1:5d8DqS60xkj9k3aXfL3+mXBH0DPYO0FQjcKosxl+b/Q=
|
||||
github.com/circonus-labs/circonus-gometrics v2.2.7+incompatible h1:Rk92ZMiCn5qFDI9nIMJiJj2cLxMaMamq4JUWI0gqU8s=
|
||||
github.com/circonus-labs/circonus-gometrics v2.2.7+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
|
|
@ -86,6 +89,7 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
|
|||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0=
|
||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
|
|
@ -146,6 +150,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||
github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
|
|
@ -225,6 +230,8 @@ github.com/hashicorp/consul/api v1.0.1 h1:LkHu3cLXjya4lgrAyZVe/CUBXgJ7AcDWKSeCjA
|
|||
github.com/hashicorp/consul/api v1.0.1/go.mod h1:LQlewHPiuaRhn1mP2XE4RrjnlRgOeWa/ZM0xWLCen2M=
|
||||
github.com/hashicorp/consul/sdk v0.1.0 h1:tTfutTNVUTDXpNM4YCImLfiiY3yCDpfgS6tNlUioIUE=
|
||||
github.com/hashicorp/consul/sdk v0.1.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
|
|
@ -235,6 +242,7 @@ github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/
|
|||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0 h1:z3ollgGRg8RjfJH6UVBaG54R70GFd++QOkvnJH3VSBY=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
|
|
@ -243,6 +251,8 @@ github.com/hashicorp/go-memdb v1.0.0 h1:K1O4N2VPndZiTrdH3lmmf5bemr9Xw81KjVwhReIU
|
|||
github.com/hashicorp/go-memdb v1.0.0/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M=
|
||||
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.0.0 h1:/gQ1sNR8/LHpoxKRQq4PmLBuacfZb4tC93e9B30o/7c=
|
||||
|
|
@ -275,6 +285,14 @@ github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG67
|
|||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf h1:U/40PQvWkaXCDdK9QHKf1pVDVcA+NIDVbzzonFGkgIA=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf/go.mod h1:BDngVi1f4UA6aJq9WYTgxhfWSE1+42xshvstLU2fRGk=
|
||||
github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=
|
||||
github.com/hashicorp/raft v1.0.2-0.20190617182316-3db06beda834 h1:rwZNxlIwa8lRM03y5QJjvw7FYt13gx8Awd0dpiQvQBk=
|
||||
github.com/hashicorp/raft v1.0.2-0.20190617182316-3db06beda834/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
|
||||
github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk=
|
||||
github.com/hashicorp/raft-snapshot v1.0.0 h1:grQa1e5WAzO9itP3PRuFyOJv4R2tey+uGRNnyptAFL0=
|
||||
github.com/hashicorp/raft-snapshot v1.0.0/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic=
|
||||
github.com/hashicorp/raft-snapshot v1.0.1 h1:cx002JsTEAfAP0pIuANlDtTXg/pi2Db6YbRRmLQTQKw=
|
||||
github.com/hashicorp/raft-snapshot v1.0.1/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic=
|
||||
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/vault-plugin-auth-alicloud v0.5.1 h1:CldlLfMGlcXy+5CvnNsOWJjE9/C1i+Nho4ClSJe+63k=
|
||||
|
|
@ -366,6 +384,7 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw=
|
||||
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
|
|
@ -429,6 +448,7 @@ github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm
|
|||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8=
|
||||
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
|
|
@ -454,6 +474,7 @@ github.com/pquerna/otp v1.1.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nh
|
|||
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612 h1:13pIdM2tpaDi4OVe24fgoIS7ZTqMt0QI+bwQsX5hq+g=
|
||||
|
|
@ -463,11 +484,13 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJ
|
|||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 h1:osmNoEW2SCW3L7EX0km2LYM8HKpNWRiouxjE3XHkyGc=
|
||||
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be h1:MoyXp/VjXUwM0GyDcdwT7Ubea2gxOSHpPaFo3qV+Y2A=
|
||||
github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
|
|
@ -594,6 +617,8 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqY
|
|||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190410170021-cc4d4f50624c h1:OUGWoQpM/o3TxM7Fp3CEqRpaYCbg4H1hOVPnZoUtr2U=
|
||||
golang.org/x/sys v0.0.0-20190410170021-cc4d4f50624c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5 h1:sM3evRHxE/1RuMe1FYAL3j7C7fUfIjkbE+NiDAYUF8U=
|
||||
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
|||
|
|
@ -5,20 +5,26 @@ import (
|
|||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/vault/cluster"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
raftlib "github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/vault/api"
|
||||
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
|
||||
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/xor"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
|
|
@ -514,6 +520,21 @@ func (r *ReplicatedTestClustersBuilder) enableDrSecondary(t testing.T, tc *vault
|
|||
EnsureCoresUnsealed(t, tc)
|
||||
}
|
||||
|
||||
func EnsureStableActiveNode(t testing.T, cluster *vault.TestCluster) {
|
||||
activeCore := DeriveActiveCore(t, cluster)
|
||||
|
||||
for i := 0; i < 30; i++ {
|
||||
leaderResp, err := activeCore.Client.Sys().Leader()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !leaderResp.IsSelf {
|
||||
t.Fatal("unstable active node")
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func DeriveActiveCore(t testing.T, cluster *vault.TestCluster) *vault.TestClusterCore {
|
||||
for i := 0; i < 10; i++ {
|
||||
for _, core := range cluster.Cores {
|
||||
|
|
@ -546,6 +567,25 @@ func DeriveStandbyCores(t testing.T, cluster *vault.TestCluster) []*vault.TestCl
|
|||
return cores
|
||||
}
|
||||
|
||||
func WaitForNCoresUnsealed(t testing.T, cluster *vault.TestCluster, n int) {
|
||||
t.Helper()
|
||||
for i := 0; i < 30; i++ {
|
||||
unsealed := 0
|
||||
for _, core := range cluster.Cores {
|
||||
if !core.Core.Sealed() {
|
||||
unsealed++
|
||||
}
|
||||
}
|
||||
|
||||
if unsealed >= n {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
t.Fatalf("%d cores were not sealed", n)
|
||||
}
|
||||
|
||||
func WaitForNCoresSealed(t testing.T, cluster *vault.TestCluster, n int) {
|
||||
t.Helper()
|
||||
for i := 0; i < 30; i++ {
|
||||
|
|
@ -622,3 +662,131 @@ func WaitForWAL(t testing.T, c *vault.TestClusterCore, wal uint64) {
|
|||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func RekeyCluster(t testing.T, cluster *vault.TestCluster) {
|
||||
client := cluster.Cores[0].Client
|
||||
|
||||
init, err := client.Sys().RekeyInit(&api.RekeyInitRequest{
|
||||
SecretShares: 5,
|
||||
SecretThreshold: 3,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var statusResp *api.RekeyUpdateResponse
|
||||
for j := 0; j < len(cluster.BarrierKeys); j++ {
|
||||
statusResp, err = client.Sys().RekeyUpdate(base64.StdEncoding.EncodeToString(cluster.BarrierKeys[j]), init.Nonce)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if statusResp == nil {
|
||||
t.Fatal("nil status response during unseal")
|
||||
}
|
||||
if statusResp.Complete {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(statusResp.KeysB64) != 5 {
|
||||
t.Fatal("wrong number of keys")
|
||||
}
|
||||
|
||||
newBarrierKeys := make([][]byte, 5)
|
||||
for i, key := range statusResp.KeysB64 {
|
||||
newBarrierKeys[i], err = base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cluster.BarrierKeys = newBarrierKeys
|
||||
}
|
||||
|
||||
func CreateRaftBackend(t testing.T, logger hclog.Logger, nodeID string) (physical.Backend, func(), error) {
|
||||
raftDir, err := ioutil.TempDir("", "vault-raft-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("raft dir: %s", raftDir)
|
||||
cleanupFunc := func() {
|
||||
os.RemoveAll(raftDir)
|
||||
}
|
||||
|
||||
logger.Info("raft dir", "dir", raftDir)
|
||||
|
||||
conf := map[string]string{
|
||||
"path": raftDir,
|
||||
"node_id": nodeID,
|
||||
}
|
||||
|
||||
backend, err := raft.NewRaftBackend(conf, logger)
|
||||
if err != nil {
|
||||
cleanupFunc()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return backend, cleanupFunc, nil
|
||||
}
|
||||
|
||||
type TestRaftServerAddressProvider struct {
|
||||
Cluster *vault.TestCluster
|
||||
}
|
||||
|
||||
func (p *TestRaftServerAddressProvider) ServerAddr(id raftlib.ServerID) (raftlib.ServerAddress, error) {
|
||||
for _, core := range p.Cluster.Cores {
|
||||
if core.NodeID == string(id) {
|
||||
parsed, err := url.Parse(core.ClusterAddr())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return raftlib.ServerAddress(parsed.Host), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("could not find cluster addr")
|
||||
}
|
||||
|
||||
func RaftClusterJoinNodes(t testing.T, cluster *vault.TestCluster) {
|
||||
addressProvider := &TestRaftServerAddressProvider{Cluster: cluster}
|
||||
|
||||
leaderCore := cluster.Cores[0]
|
||||
leaderAPI := leaderCore.Client.Address()
|
||||
vault.UpdateClusterAddrForTests = true
|
||||
|
||||
// Seal the leader so we can install an address provider
|
||||
{
|
||||
EnsureCoreSealed(t, leaderCore)
|
||||
leaderCore.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
|
||||
cluster.UnsealCore(t, leaderCore)
|
||||
vault.TestWaitActive(t, leaderCore.Core)
|
||||
}
|
||||
|
||||
// Join core1
|
||||
{
|
||||
core := cluster.Cores[1]
|
||||
core.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
|
||||
_, err := core.JoinRaftCluster(namespace.RootContext(context.Background()), leaderAPI, leaderCore.TLSConfig, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cluster.UnsealCore(t, core)
|
||||
|
||||
}
|
||||
|
||||
// Join core2
|
||||
{
|
||||
core := cluster.Cores[2]
|
||||
core.UnderlyingRawStorage.(*raft.RaftBackend).SetServerAddressProvider(addressProvider)
|
||||
_, err := core.JoinRaftCluster(namespace.RootContext(context.Background()), leaderAPI, leaderCore.TLSConfig, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cluster.UnsealCore(t, core)
|
||||
}
|
||||
|
||||
WaitForNCoresUnsealed(t, cluster, 3)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ var (
|
|||
// perfStandbyAlwaysForwardPaths is used to check a requested path against
|
||||
// the always forward list
|
||||
perfStandbyAlwaysForwardPaths = pathmanager.New()
|
||||
alwaysRedirectPaths = pathmanager.New()
|
||||
|
||||
injectDataIntoTopRoutes = []string{
|
||||
"/v1/sys/audit",
|
||||
|
|
@ -95,6 +96,13 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
alwaysRedirectPaths.AddPaths([]string{
|
||||
"sys/storage/raft/snapshot",
|
||||
"sys/storage/raft/snapshot-force",
|
||||
})
|
||||
}
|
||||
|
||||
// Handler returns an http.Handler for the API. This can be used on
|
||||
// its own to mount the Vault API within another web server.
|
||||
func Handler(props *vault.HandlerProperties) http.Handler {
|
||||
|
|
@ -117,6 +125,7 @@ func Handler(props *vault.HandlerProperties) http.Handler {
|
|||
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, true)))
|
||||
mux.Handle("/v1/sys/storage/raft/join", handleSysRaftJoin(core))
|
||||
for _, path := range injectDataIntoTopRoutes {
|
||||
mux.Handle(path, handleRequestForwarding(core, handleLogicalWithInjector(core)))
|
||||
}
|
||||
|
|
@ -491,7 +500,7 @@ func handleRequestForwarding(core *vault.Core, handler http.Handler) http.Handle
|
|||
}
|
||||
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])
|
||||
switch {
|
||||
case !perfStandbyAlwaysForwardPaths.HasPath(path):
|
||||
case !perfStandbyAlwaysForwardPaths.HasPath(path) && !alwaysRedirectPaths.HasPath(path):
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
case strings.HasPrefix(path, "auth/token/create/"):
|
||||
|
|
@ -545,6 +554,17 @@ func forwardRequest(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
ns, err := namespace.FromContext(r.Context())
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])
|
||||
if alwaysRedirectPaths.HasPath(path) {
|
||||
respondStandby(core, w, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt forwarding the request. If we cannot forward -- perhaps it's
|
||||
// been disabled on the active node -- this will return with an
|
||||
// ErrCannotForward and we simply fall back
|
||||
|
|
@ -615,6 +635,14 @@ func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *l
|
|||
}
|
||||
}
|
||||
|
||||
// If vault's core has already written to the response writer do not add any
|
||||
// additional output. Headers have already been sent. If the response writer
|
||||
// is set but has not been written to it likely means there was some kind of
|
||||
// error
|
||||
if r.ResponseWriter != nil && r.ResponseWriter.Written() {
|
||||
return nil, true, false
|
||||
}
|
||||
|
||||
if respondErrorCommon(w, r, resp, err) {
|
||||
return resp, false, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques
|
|||
|
||||
var data map[string]interface{}
|
||||
var origBody io.ReadCloser
|
||||
var requestReader io.ReadCloser
|
||||
var responseWriter io.Writer
|
||||
|
||||
// Determine the operation
|
||||
var op logical.Operation
|
||||
|
|
@ -75,18 +77,29 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques
|
|||
data = getData
|
||||
}
|
||||
}
|
||||
if path == "sys/storage/raft/snapshot" {
|
||||
responseWriter = w
|
||||
}
|
||||
|
||||
case "POST", "PUT":
|
||||
op = logical.UpdateOperation
|
||||
// Parse the request if we can
|
||||
if op == logical.UpdateOperation {
|
||||
origBody, err = parseRequest(core, r, w, &data)
|
||||
if err == io.EOF {
|
||||
data = nil
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, http.StatusBadRequest, err
|
||||
// If we are uploading a snapshot we don't want to parse it. Instead
|
||||
// we will simply add the request body to the logical request object
|
||||
// for later consumption.
|
||||
if path == "sys/storage/raft/snapshot" || path == "sys/storage/raft/snapshot-force" {
|
||||
requestReader = r.Body
|
||||
origBody = r.Body
|
||||
} else {
|
||||
origBody, err = parseRequest(core, r, w, &data)
|
||||
if err == io.EOF {
|
||||
data = nil
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, http.StatusBadRequest, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +149,12 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques
|
|||
return nil, nil, http.StatusBadRequest, errwrap.Wrapf(fmt.Sprintf(`failed to parse %s header: {{err}}`, PolicyOverrideHeaderName), err)
|
||||
}
|
||||
|
||||
if requestReader != nil {
|
||||
req.RequestReader = requestReader
|
||||
}
|
||||
if responseWriter != nil {
|
||||
req.ResponseWriter = logical.NewHTTPResponseWriter(responseWriter)
|
||||
}
|
||||
return req, origBody, 0, nil
|
||||
}
|
||||
|
||||
|
|
@ -294,6 +313,12 @@ func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request
|
|||
var httpResp *logical.HTTPResponse
|
||||
var ret interface{}
|
||||
|
||||
// If vault's core has already written to the response writer do not add any
|
||||
// additional output. Headers have already been sent.
|
||||
if req != nil && req.ResponseWriter != nil && req.ResponseWriter.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if resp.Redirect != "" {
|
||||
// If we have a redirect, redirect! We use a 307 code
|
||||
|
|
|
|||
50
http/sys_raft.go
Normal file
50
http/sys_raft.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func handleSysRaftJoin(core *vault.Core) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "POST", "PUT":
|
||||
handleSysRaftJoinPost(core, w, r)
|
||||
default:
|
||||
respondError(w, http.StatusMethodNotAllowed, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handleSysRaftJoinPost(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request
|
||||
var req JoinRequest
|
||||
if _, err := parseRequest(core, r, w, &req); err != nil && err != io.EOF {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
joined, err := core.JoinRaftCluster(context.Background(), req.LeaderAddr, nil, req.Retry)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := JoinResponse{
|
||||
Joined: joined,
|
||||
}
|
||||
respondOk(w, resp)
|
||||
}
|
||||
|
||||
type JoinResponse struct {
|
||||
Joined bool `json:"joined"`
|
||||
}
|
||||
|
||||
type JoinRequest struct {
|
||||
LeaderAddr string `json:"leader_api_addr"`
|
||||
CACert string `json:"ca_cert":`
|
||||
Retry bool `json:"retry"`
|
||||
}
|
||||
646
physical/raft/fsm.go
Normal file
646
physical/raft/fsm.go
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
protoio "github.com/gogo/protobuf/io"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/sdk/plugin/pb"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const (
|
||||
deleteOp uint32 = 1 << iota
|
||||
putOp
|
||||
restoreCallbackOp
|
||||
)
|
||||
|
||||
var (
|
||||
// dataBucketName is the value we use for the bucket
|
||||
dataBucketName = []byte("data")
|
||||
configBucketName = []byte("config")
|
||||
latestIndexKey = []byte("latest_indexes")
|
||||
latestConfigKey = []byte("latest_config")
|
||||
)
|
||||
|
||||
// Verify FSM satisfies the correct interfaces
|
||||
var _ physical.Backend = (*FSM)(nil)
|
||||
var _ physical.Transactional = (*FSM)(nil)
|
||||
var _ raft.FSM = (*FSM)(nil)
|
||||
var _ raft.ConfigurationStore = (*FSM)(nil)
|
||||
|
||||
type restoreCallback func() error
|
||||
|
||||
// FSMApplyResponse is returned from an FSM apply. It indicates if the apply was
|
||||
// successful or not.
|
||||
type FSMApplyResponse struct {
|
||||
Success bool
|
||||
}
|
||||
|
||||
// FSM is Vault's primary state storage. It writes updates to an bolt db file
|
||||
// that lives on local disk. FSM implements raft.FSM and physical.Backend
|
||||
// interfaces.
|
||||
type FSM struct {
|
||||
l sync.RWMutex
|
||||
path string
|
||||
logger log.Logger
|
||||
permitPool *physical.PermitPool
|
||||
noopRestore bool
|
||||
|
||||
db *bolt.DB
|
||||
|
||||
// retoreCb is called after we've restored a snapshot
|
||||
restoreCb restoreCallback
|
||||
|
||||
// latestIndex and latestTerm are the term and index of the last log we
|
||||
// received
|
||||
latestIndex *uint64
|
||||
latestTerm *uint64
|
||||
// latestConfig is the latest server configuration we've seen
|
||||
latestConfig atomic.Value
|
||||
|
||||
// This is just used in tests to disable to storing the latest indexes and
|
||||
// configs so we can conform to the standard backend tests, which expect to
|
||||
// additional state in the backend.
|
||||
storeLatestState bool
|
||||
}
|
||||
|
||||
// NewFSM constructs a FSM using the given directory
|
||||
func NewFSM(conf map[string]string, logger log.Logger) (*FSM, error) {
|
||||
path, ok := conf["path"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'path' must be set")
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(path, "vault.db")
|
||||
|
||||
boltDB, err := bolt.Open(dbPath, 0666, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the latest term, index, and config values
|
||||
latestTerm := new(uint64)
|
||||
latestIndex := new(uint64)
|
||||
latestConfig := atomic.Value{}
|
||||
atomic.StoreUint64(latestTerm, 0)
|
||||
atomic.StoreUint64(latestIndex, 0)
|
||||
latestConfig.Store((*ConfigurationValue)(nil))
|
||||
|
||||
err = boltDB.Update(func(tx *bolt.Tx) error {
|
||||
// make sure we have the necessary buckets created
|
||||
_, err := tx.CreateBucketIfNotExists(dataBucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create bucket: %v", err)
|
||||
}
|
||||
b, err := tx.CreateBucketIfNotExists(configBucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create bucket: %v", err)
|
||||
}
|
||||
// Read in our latest index and term and populate it inmemory
|
||||
val := b.Get(latestIndexKey)
|
||||
if val != nil {
|
||||
var latest IndexValue
|
||||
err := proto.Unmarshal(val, &latest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.StoreUint64(latestTerm, latest.Term)
|
||||
atomic.StoreUint64(latestIndex, latest.Index)
|
||||
}
|
||||
|
||||
// Read in our latest config and populate it inmemory
|
||||
val = b.Get(latestConfigKey)
|
||||
if val != nil {
|
||||
var latest ConfigurationValue
|
||||
err := proto.Unmarshal(val, &latest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
latestConfig.Store(&latest)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storeLatestState := true
|
||||
if _, ok := conf["doNotStoreLatestState"]; ok {
|
||||
storeLatestState = false
|
||||
}
|
||||
|
||||
return &FSM{
|
||||
path: path,
|
||||
logger: logger,
|
||||
permitPool: physical.NewPermitPool(physical.DefaultParallelOperations),
|
||||
|
||||
db: boltDB,
|
||||
latestTerm: latestTerm,
|
||||
latestIndex: latestIndex,
|
||||
latestConfig: latestConfig,
|
||||
storeLatestState: storeLatestState,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LatestState returns the latest index and configuration values we have seen on
|
||||
// this FSM.
|
||||
func (f *FSM) LatestState() (*IndexValue, *ConfigurationValue) {
|
||||
return &IndexValue{
|
||||
Term: atomic.LoadUint64(f.latestTerm),
|
||||
Index: atomic.LoadUint64(f.latestIndex),
|
||||
}, f.latestConfig.Load().(*ConfigurationValue)
|
||||
}
|
||||
|
||||
func (f *FSM) witnessIndex(i *IndexValue) {
|
||||
seen, _ := f.LatestState()
|
||||
if seen.Index < i.Index {
|
||||
atomic.StoreUint64(f.latestIndex, i.Index)
|
||||
atomic.StoreUint64(f.latestTerm, i.Term)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FSM) witnessSnapshot(index, term, configurationIndex uint64, configuration raft.Configuration) error {
|
||||
var indexBytes []byte
|
||||
latestIndex, _ := f.LatestState()
|
||||
|
||||
latestIndex.Index = index
|
||||
latestIndex.Term = term
|
||||
|
||||
var err error
|
||||
indexBytes, err = proto.Marshal(latestIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protoConfig := raftConfigurationToProtoConfiguration(configurationIndex, configuration)
|
||||
configBytes, err := proto.Marshal(protoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.storeLatestState {
|
||||
err = f.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(configBucketName)
|
||||
err := b.Put(latestConfigKey, configBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Put(latestIndexKey, indexBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
atomic.StoreUint64(f.latestIndex, index)
|
||||
atomic.StoreUint64(f.latestTerm, term)
|
||||
f.latestConfig.Store(protoConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the bolt file.
|
||||
func (f *FSM) Delete(ctx context.Context, path string) error {
|
||||
defer metrics.MeasureSince([]string{"raft", "delete"}, time.Now())
|
||||
|
||||
f.permitPool.Acquire()
|
||||
defer f.permitPool.Release()
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
return f.db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(dataBucketName).Delete([]byte(path))
|
||||
})
|
||||
}
|
||||
|
||||
// Get retrieves the value at the given path from the bolt file.
|
||||
func (f *FSM) Get(ctx context.Context, path string) (*physical.Entry, error) {
|
||||
defer metrics.MeasureSince([]string{"raft", "get"}, time.Now())
|
||||
|
||||
f.permitPool.Acquire()
|
||||
defer f.permitPool.Release()
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
var valCopy []byte
|
||||
var found bool
|
||||
|
||||
err := f.db.View(func(tx *bolt.Tx) error {
|
||||
|
||||
value := tx.Bucket(dataBucketName).Get([]byte(path))
|
||||
if value != nil {
|
||||
found = true
|
||||
valCopy = make([]byte, len(value))
|
||||
copy(valCopy, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &physical.Entry{
|
||||
Key: path,
|
||||
Value: valCopy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Put writes the given entry to the bolt file.
|
||||
func (f *FSM) Put(ctx context.Context, entry *physical.Entry) error {
|
||||
defer metrics.MeasureSince([]string{"raft", "put"}, time.Now())
|
||||
|
||||
f.permitPool.Acquire()
|
||||
defer f.permitPool.Release()
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
// Start a write transaction.
|
||||
return f.db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(dataBucketName).Put([]byte(entry.Key), entry.Value)
|
||||
})
|
||||
}
|
||||
|
||||
// List retrieves the set of keys with the given prefix from the bolt file.
|
||||
func (f *FSM) List(ctx context.Context, prefix string) ([]string, error) {
|
||||
defer metrics.MeasureSince([]string{"raft", "list"}, time.Now())
|
||||
|
||||
f.permitPool.Acquire()
|
||||
defer f.permitPool.Release()
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
var keys []string
|
||||
|
||||
err := f.db.View(func(tx *bolt.Tx) error {
|
||||
// Assume bucket exists and has keys
|
||||
c := tx.Bucket(dataBucketName).Cursor()
|
||||
|
||||
prefixBytes := []byte(prefix)
|
||||
for k, _ := c.Seek(prefixBytes); k != nil && bytes.HasPrefix(k, prefixBytes); k, _ = c.Next() {
|
||||
key := string(k)
|
||||
key = strings.TrimPrefix(key, prefix)
|
||||
if i := strings.Index(key, "/"); i == -1 {
|
||||
// Add objects only from the current 'folder'
|
||||
keys = append(keys, key)
|
||||
} else if i != -1 {
|
||||
// Add truncated 'folder' paths
|
||||
keys = strutil.AppendIfMissing(keys, string(key[:i+1]))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// Transaction writes all the operations in the provided transaction to the bolt
|
||||
// file.
|
||||
func (f *FSM) Transaction(ctx context.Context, txns []*physical.TxnEntry) error {
|
||||
f.permitPool.Acquire()
|
||||
defer f.permitPool.Release()
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
// TODO: should this be a Batch?
|
||||
// Start a write transaction.
|
||||
err := f.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(dataBucketName)
|
||||
for _, txn := range txns {
|
||||
var err error
|
||||
switch txn.Operation {
|
||||
case physical.PutOperation:
|
||||
err = b.Put([]byte(txn.Entry.Key), txn.Entry.Value)
|
||||
case physical.DeleteOperation:
|
||||
err = b.Delete([]byte(txn.Entry.Key))
|
||||
default:
|
||||
return fmt.Errorf("%q is not a supported transaction operation", txn.Operation)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply will apply a log value to the FSM. This is called from the raft
|
||||
// library.
|
||||
func (f *FSM) Apply(log *raft.Log) interface{} {
|
||||
command := &LogData{}
|
||||
err := proto.Unmarshal(log.Data, command)
|
||||
if err != nil {
|
||||
panic("error proto unmarshaling log data")
|
||||
}
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
// Only advance latest pointer if this log has a higher index value than
|
||||
// what we have seen in the past.
|
||||
var logIndex []byte
|
||||
latestIndex, _ := f.LatestState()
|
||||
if latestIndex.Index < log.Index {
|
||||
logIndex, err = proto.Marshal(&IndexValue{
|
||||
Term: log.Term,
|
||||
Index: log.Index,
|
||||
})
|
||||
if err != nil {
|
||||
panic("failed to store data")
|
||||
}
|
||||
}
|
||||
|
||||
err = f.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(dataBucketName)
|
||||
for _, op := range command.Operations {
|
||||
var err error
|
||||
switch op.OpType {
|
||||
case putOp:
|
||||
err = b.Put([]byte(op.Key), op.Value)
|
||||
case deleteOp:
|
||||
err = b.Delete([]byte(op.Key))
|
||||
case restoreCallbackOp:
|
||||
if f.restoreCb != nil {
|
||||
// Kick off the restore callback function in a go routine
|
||||
go f.restoreCb()
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%q is not a supported transaction operation", op.OpType)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: benchmark so we can know how much time this adds
|
||||
if f.storeLatestState && len(logIndex) > 0 {
|
||||
b := tx.Bucket(configBucketName)
|
||||
err = b.Put(latestIndexKey, logIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("failed to store data")
|
||||
}
|
||||
|
||||
// If we advanced the latest value, update the in-memory representation too.
|
||||
if len(logIndex) > 0 {
|
||||
atomic.StoreUint64(f.latestTerm, log.Term)
|
||||
atomic.StoreUint64(f.latestIndex, log.Index)
|
||||
}
|
||||
|
||||
return &FSMApplyResponse{
|
||||
Success: true,
|
||||
}
|
||||
}
|
||||
|
||||
type writeErrorCloser interface {
|
||||
io.WriteCloser
|
||||
CloseWithError(error) error
|
||||
}
|
||||
|
||||
// writeTo will copy the FSM's content to a remote sink. The data is written
|
||||
// twice, once for use in determining various metadata attributes of the dataset
|
||||
// (size, checksum, etc) and a second for the sink of the data. We also use a
|
||||
// proto delimited writer so we can stream proto messages to the sink.
|
||||
func (f *FSM) writeTo(ctx context.Context, metaSink writeErrorCloser, sink writeErrorCloser) {
|
||||
protoWriter := protoio.NewDelimitedWriter(sink)
|
||||
metadataProtoWriter := protoio.NewDelimitedWriter(metaSink)
|
||||
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
err := f.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(dataBucketName)
|
||||
|
||||
c := b.Cursor()
|
||||
|
||||
// Do the first scan of the data for metadata purposes.
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
err := metadataProtoWriter.WriteMsg(&pb.StorageEntry{
|
||||
Key: string(k),
|
||||
Value: v,
|
||||
})
|
||||
if err != nil {
|
||||
metaSink.CloseWithError(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
metaSink.Close()
|
||||
|
||||
// Do the second scan for copy purposes.
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
err := protoWriter.WriteMsg(&pb.StorageEntry{
|
||||
Key: string(k),
|
||||
Value: v,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
sink.CloseWithError(err)
|
||||
}
|
||||
|
||||
// Snapshot implements the FSM interface. It returns a noop snapshot object.
|
||||
func (f *FSM) Snapshot() (raft.FSMSnapshot, error) {
|
||||
return &noopSnapshotter{}, nil
|
||||
}
|
||||
|
||||
// SetNoopRestore is used to disable restore operations on raft startup. Because
|
||||
// we are using persistent storage in our FSM we do not need to issue a restore
|
||||
// on startup.
|
||||
func (f *FSM) SetNoopRestore(enabled bool) {
|
||||
f.l.Lock()
|
||||
f.noopRestore = enabled
|
||||
f.l.Unlock()
|
||||
}
|
||||
|
||||
// Restore reads data from the provided reader and writes it into the FSM. It
|
||||
// first deletes the existing bucket to clear all existing data, then recreates
|
||||
// it so we can copy in the snapshot.
|
||||
func (f *FSM) Restore(r io.ReadCloser) error {
|
||||
if f.noopRestore == true {
|
||||
return nil
|
||||
}
|
||||
|
||||
protoReader := protoio.NewDelimitedReader(r, math.MaxInt64)
|
||||
defer protoReader.Close()
|
||||
|
||||
f.l.Lock()
|
||||
defer f.l.Unlock()
|
||||
|
||||
// Start a write transaction.
|
||||
err := f.db.Update(func(tx *bolt.Tx) error {
|
||||
err := tx.DeleteBucket(dataBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := tx.CreateBucket(dataBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
s := new(pb.StorageEntry)
|
||||
err := protoReader.ReadMsg(s)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.Put([]byte(s.Key), s.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
f.logger.Error("could not restore snapshot", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// noopSnapshotter implements the fsm.Snapshot interface. It doesn't do anything
|
||||
// since our SnapshotStore reads data out of the FSM on Open().
|
||||
type noopSnapshotter struct{}
|
||||
|
||||
// Persist doesn't do anything.
|
||||
func (s *noopSnapshotter) Persist(sink raft.SnapshotSink) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release doesn't do anything.
|
||||
func (s *noopSnapshotter) Release() {}
|
||||
|
||||
// StoreConfig satisfies the raft.ConfigurationStore interface and persists the
|
||||
// latest raft server configuration to the bolt file.
|
||||
func (f *FSM) StoreConfiguration(index uint64, configuration raft.Configuration) {
|
||||
f.l.RLock()
|
||||
defer f.l.RUnlock()
|
||||
|
||||
var indexBytes []byte
|
||||
latestIndex, _ := f.LatestState()
|
||||
// Only write the new index if we are advancing the pointer
|
||||
if index > latestIndex.Index {
|
||||
latestIndex.Index = index
|
||||
|
||||
var err error
|
||||
indexBytes, err = proto.Marshal(latestIndex)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to marshal latest index: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
protoConfig := raftConfigurationToProtoConfiguration(index, configuration)
|
||||
configBytes, err := proto.Marshal(protoConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to marshal config: %v", err))
|
||||
}
|
||||
|
||||
if f.storeLatestState {
|
||||
err = f.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(configBucketName)
|
||||
err := b.Put(latestConfigKey, configBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: benchmark so we can know how much time this adds
|
||||
if len(indexBytes) > 0 {
|
||||
err = b.Put(latestIndexKey, indexBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to store latest configuration: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
f.witnessIndex(latestIndex)
|
||||
f.latestConfig.Store(protoConfig)
|
||||
}
|
||||
|
||||
// raftConfigurationToProtoConfiguration converts a raft configuration object to
|
||||
// a proto value.
|
||||
func raftConfigurationToProtoConfiguration(index uint64, configuration raft.Configuration) *ConfigurationValue {
|
||||
servers := make([]*Server, len(configuration.Servers))
|
||||
for i, s := range configuration.Servers {
|
||||
servers[i] = &Server{
|
||||
Suffrage: int32(s.Suffrage),
|
||||
Id: string(s.ID),
|
||||
Address: string(s.Address),
|
||||
}
|
||||
}
|
||||
return &ConfigurationValue{
|
||||
Index: index,
|
||||
Servers: servers,
|
||||
}
|
||||
}
|
||||
|
||||
// protoConfigurationToRaftConfiguration converts a proto configuration object
|
||||
// to a raft object.
|
||||
func protoConfigurationToRaftConfiguration(configuration *ConfigurationValue) (uint64, raft.Configuration) {
|
||||
servers := make([]raft.Server, len(configuration.Servers))
|
||||
for i, s := range configuration.Servers {
|
||||
servers[i] = raft.Server{
|
||||
Suffrage: raft.ServerSuffrage(s.Suffrage),
|
||||
ID: raft.ServerID(s.Id),
|
||||
Address: raft.ServerAddress(s.Address),
|
||||
}
|
||||
}
|
||||
return configuration.Index, raft.Configuration{
|
||||
Servers: servers,
|
||||
}
|
||||
}
|
||||
271
physical/raft/logstore/bolt_store.go
Normal file
271
physical/raft/logstore/bolt_store.go
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
package logstore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/raft"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const (
|
||||
// Permissions to use on the db file. This is only used if the
|
||||
// database file does not exist and needs to be created.
|
||||
dbFileMode = 0600
|
||||
)
|
||||
|
||||
var (
|
||||
// Bucket names we perform transactions in
|
||||
dbLogs = []byte("logs")
|
||||
dbConf = []byte("conf")
|
||||
|
||||
// An error indicating a given key does not exist
|
||||
ErrKeyNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
// BoltStore provides access to BoltDB for Raft to store and retrieve
|
||||
// log entries. It also provides key/value storage, and can be used as
|
||||
// a LogStore and StableStore.
|
||||
type BoltStore struct {
|
||||
// conn is the underlying handle to the db.
|
||||
conn *bolt.DB
|
||||
|
||||
// The path to the Bolt database file
|
||||
path string
|
||||
}
|
||||
|
||||
// Options contains all the configuraiton used to open the BoltDB
|
||||
type Options struct {
|
||||
// Path is the file path to the BoltDB to use
|
||||
Path string
|
||||
|
||||
// BoltOptions contains any specific BoltDB options you might
|
||||
// want to specify [e.g. open timeout]
|
||||
BoltOptions *bolt.Options
|
||||
|
||||
// NoSync causes the database to skip fsync calls after each
|
||||
// write to the log. This is unsafe, so it should be used
|
||||
// with caution.
|
||||
NoSync bool
|
||||
}
|
||||
|
||||
// readOnly returns true if the contained bolt options say to open
|
||||
// the DB in readOnly mode [this can be useful to tools that want
|
||||
// to examine the log]
|
||||
func (o *Options) readOnly() bool {
|
||||
return o != nil && o.BoltOptions != nil && o.BoltOptions.ReadOnly
|
||||
}
|
||||
|
||||
// NewBoltStore takes a file path and returns a connected Raft backend.
|
||||
func NewBoltStore(path string) (*BoltStore, error) {
|
||||
return New(Options{Path: path})
|
||||
}
|
||||
|
||||
// New uses the supplied options to open the BoltDB and prepare it for use as a raft backend.
|
||||
func New(options Options) (*BoltStore, error) {
|
||||
// Try to connect
|
||||
handle, err := bolt.Open(options.Path, dbFileMode, options.BoltOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handle.NoSync = options.NoSync
|
||||
|
||||
// Create the new store
|
||||
store := &BoltStore{
|
||||
conn: handle,
|
||||
path: options.Path,
|
||||
}
|
||||
|
||||
// If the store was opened read-only, don't try and create buckets
|
||||
if !options.readOnly() {
|
||||
// Set up our buckets
|
||||
if err := store.initialize(); err != nil {
|
||||
store.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// initialize is used to set up all of the buckets.
|
||||
func (b *BoltStore) initialize() error {
|
||||
tx, err := b.conn.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Create all the buckets
|
||||
if _, err := tx.CreateBucketIfNotExists(dbLogs); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.CreateBucketIfNotExists(dbConf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Close is used to gracefully close the DB connection.
|
||||
func (b *BoltStore) Close() error {
|
||||
return b.conn.Close()
|
||||
}
|
||||
|
||||
// FirstIndex returns the first known index from the Raft log.
|
||||
func (b *BoltStore) FirstIndex() (uint64, error) {
|
||||
tx, err := b.conn.Begin(false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
curs := tx.Bucket(dbLogs).Cursor()
|
||||
if first, _ := curs.First(); first == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
return bytesToUint64(first), nil
|
||||
}
|
||||
}
|
||||
|
||||
// LastIndex returns the last known index from the Raft log.
|
||||
func (b *BoltStore) LastIndex() (uint64, error) {
|
||||
tx, err := b.conn.Begin(false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
curs := tx.Bucket(dbLogs).Cursor()
|
||||
if last, _ := curs.Last(); last == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
return bytesToUint64(last), nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetLog is used to retrieve a log from BoltDB at a given index.
|
||||
func (b *BoltStore) GetLog(idx uint64, log *raft.Log) error {
|
||||
tx, err := b.conn.Begin(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
bucket := tx.Bucket(dbLogs)
|
||||
val := bucket.Get(uint64ToBytes(idx))
|
||||
|
||||
if val == nil {
|
||||
return raft.ErrLogNotFound
|
||||
}
|
||||
|
||||
return decodeMsgPack(val, log)
|
||||
}
|
||||
|
||||
// StoreLog is used to store a single raft log
|
||||
func (b *BoltStore) StoreLog(log *raft.Log) error {
|
||||
return b.StoreLogs([]*raft.Log{log})
|
||||
}
|
||||
|
||||
// StoreLogs is used to store a set of raft logs
|
||||
func (b *BoltStore) StoreLogs(logs []*raft.Log) error {
|
||||
tx, err := b.conn.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
for _, log := range logs {
|
||||
key := uint64ToBytes(log.Index)
|
||||
val, err := encodeMsgPack(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucket := tx.Bucket(dbLogs)
|
||||
if err := bucket.Put(key, val.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// DeleteRange is used to delete logs within a given range inclusively.
|
||||
func (b *BoltStore) DeleteRange(min, max uint64) error {
|
||||
minKey := uint64ToBytes(min)
|
||||
|
||||
tx, err := b.conn.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
curs := tx.Bucket(dbLogs).Cursor()
|
||||
for k, _ := curs.Seek(minKey); k != nil; k, _ = curs.Next() {
|
||||
// Handle out-of-range log index
|
||||
if bytesToUint64(k) > max {
|
||||
break
|
||||
}
|
||||
|
||||
// Delete in-range log index
|
||||
if err := curs.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Set is used to set a key/value set outside of the raft log
|
||||
func (b *BoltStore) Set(k, v []byte) error {
|
||||
tx, err := b.conn.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
bucket := tx.Bucket(dbConf)
|
||||
if err := bucket.Put(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Get is used to retrieve a value from the k/v store by key
|
||||
func (b *BoltStore) Get(k []byte) ([]byte, error) {
|
||||
tx, err := b.conn.Begin(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
bucket := tx.Bucket(dbConf)
|
||||
val := bucket.Get(k)
|
||||
|
||||
if val == nil {
|
||||
return nil, ErrKeyNotFound
|
||||
}
|
||||
|
||||
return append([]byte(nil), val...), nil
|
||||
}
|
||||
|
||||
// SetUint64 is like Set, but handles uint64 values
|
||||
func (b *BoltStore) SetUint64(key []byte, val uint64) error {
|
||||
return b.Set(key, uint64ToBytes(val))
|
||||
}
|
||||
|
||||
// GetUint64 is like Get, but handles uint64 values
|
||||
func (b *BoltStore) GetUint64(key []byte) (uint64, error) {
|
||||
val, err := b.Get(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return bytesToUint64(val), nil
|
||||
}
|
||||
|
||||
// Sync performs an fsync on the database file handle. This is not necessary
|
||||
// under normal operation unless NoSync is enabled, in which this forces the
|
||||
// database file to sync against the disk.
|
||||
func (b *BoltStore) Sync() error {
|
||||
return b.conn.Sync()
|
||||
}
|
||||
37
physical/raft/logstore/util.go
Normal file
37
physical/raft/logstore/util.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package logstore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/hashicorp/go-msgpack/codec"
|
||||
)
|
||||
|
||||
// Decode reverses the encode operation on a byte slice input
|
||||
func decodeMsgPack(buf []byte, out interface{}) error {
|
||||
r := bytes.NewBuffer(buf)
|
||||
hd := codec.MsgpackHandle{}
|
||||
dec := codec.NewDecoder(r, &hd)
|
||||
return dec.Decode(out)
|
||||
}
|
||||
|
||||
// Encode writes an encoded object to a new bytes buffer
|
||||
func encodeMsgPack(in interface{}) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
hd := codec.MsgpackHandle{}
|
||||
enc := codec.NewEncoder(buf, &hd)
|
||||
err := enc.Encode(in)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// Converts bytes to an integer
|
||||
func bytesToUint64(b []byte) uint64 {
|
||||
return binary.BigEndian.Uint64(b)
|
||||
}
|
||||
|
||||
// Converts a uint to a byte slice
|
||||
func uint64ToBytes(u uint64) []byte {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, u)
|
||||
return buf
|
||||
}
|
||||
917
physical/raft/raft.go
Normal file
917
physical/raft/raft.go
Normal file
|
|
@ -0,0 +1,917 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/raft"
|
||||
snapshot "github.com/hashicorp/raft-snapshot"
|
||||
raftboltdb "github.com/hashicorp/vault/physical/raft/logstore"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/vault/cluster"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
)
|
||||
|
||||
// Verify RaftBackend satisfies the correct interfaces
|
||||
var _ physical.Backend = (*RaftBackend)(nil)
|
||||
var _ physical.Transactional = (*RaftBackend)(nil)
|
||||
|
||||
var (
|
||||
// raftLogCacheSize is the maximum number of logs to cache in-memory.
|
||||
// This is used to reduce disk I/O for the recently committed entries.
|
||||
raftLogCacheSize = 512
|
||||
|
||||
raftState = "raft/"
|
||||
peersFileName = "peers.json"
|
||||
snapshotsRetained = 2
|
||||
|
||||
// Set a max size of 512kb
|
||||
maxCommandSizeBytes = 512 * 1024
|
||||
|
||||
// ErrCommandTooLarge is returned when the backend tries to apply a log
|
||||
// greater than the max allowed size.
|
||||
ErrCommandTooLarge = fmt.Errorf("%s: exceeds %d byte limit", physical.ErrValueTooLarge, maxCommandSizeBytes)
|
||||
)
|
||||
|
||||
// RaftBackend implements the backend interfaces and uses the raft protocol to
|
||||
// persist writes to the FSM.
|
||||
type RaftBackend struct {
|
||||
logger log.Logger
|
||||
conf map[string]string
|
||||
l sync.RWMutex
|
||||
|
||||
// fsm is the state store for vault's data
|
||||
fsm *FSM
|
||||
|
||||
// raft is the instance of raft we will operate on.
|
||||
raft *raft.Raft
|
||||
|
||||
// raftNotifyCh is used to receive updates about leadership changes
|
||||
// regarding this node.
|
||||
raftNotifyCh chan bool
|
||||
|
||||
// streamLayer is the network layer used to connect the nodes in the raft
|
||||
// cluster.
|
||||
streamLayer *raftLayer
|
||||
|
||||
// raftTransport is the transport layer that the raft library uses for RPC
|
||||
// communication.
|
||||
raftTransport raft.Transport
|
||||
|
||||
// snapStore is our snapshot mechanism.
|
||||
snapStore raft.SnapshotStore
|
||||
|
||||
// logStore is used by the raft library to store the raft logs in durable
|
||||
// storage.
|
||||
logStore raft.LogStore
|
||||
|
||||
// stableStore is used by the raft library to store additional metadata in
|
||||
// durable storage.
|
||||
stableStore raft.StableStore
|
||||
|
||||
// bootstrapConfig is only set when this node needs to be bootstrapped upon
|
||||
// startup.
|
||||
bootstrapConfig *raft.Configuration
|
||||
|
||||
// dataDir is the location on the local filesystem that raft and FSM data
|
||||
// will be stored.
|
||||
dataDir string
|
||||
|
||||
// localID is the ID for this node. This can either be configured in the
|
||||
// config file, via a file on disk, or is otherwise randomly generated.
|
||||
localID string
|
||||
|
||||
// serverAddressProvider is used to map server IDs to addresses.
|
||||
serverAddressProvider raft.ServerAddressProvider
|
||||
}
|
||||
|
||||
// EnsurePath is used to make sure a path exists
|
||||
func EnsurePath(path string, dir bool) error {
|
||||
if !dir {
|
||||
path = filepath.Dir(path)
|
||||
}
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
|
||||
// NewRaftBackend constructs a RaftBackend using the given directory
|
||||
func NewRaftBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
|
||||
// Create the FSM.
|
||||
var err error
|
||||
fsm, err := NewFSM(conf, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create fsm: %v", err)
|
||||
}
|
||||
|
||||
path, ok := conf["path"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("'path' must be set")
|
||||
}
|
||||
|
||||
// Build an all in-memory setup for dev mode, otherwise prepare a full
|
||||
// disk-based setup.
|
||||
var log raft.LogStore
|
||||
var stable raft.StableStore
|
||||
var snap raft.SnapshotStore
|
||||
var devMode bool
|
||||
if devMode {
|
||||
store := raft.NewInmemStore()
|
||||
stable = store
|
||||
log = store
|
||||
snap = raft.NewInmemSnapshotStore()
|
||||
} else {
|
||||
// Create the base raft path.
|
||||
path := filepath.Join(path, raftState)
|
||||
if err := EnsurePath(path, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the backend raft store for logs and stable storage.
|
||||
store, err := raftboltdb.NewBoltStore(filepath.Join(path, "raft.db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stable = store
|
||||
|
||||
// Wrap the store in a LogCache to improve performance.
|
||||
cacheStore, err := raft.NewLogCache(raftLogCacheSize, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log = cacheStore
|
||||
|
||||
// Create the snapshot store.
|
||||
snapshots, err := NewBoltSnapshotStore(path, snapshotsRetained, logger.Named("snapshot"), fsm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snap = snapshots
|
||||
}
|
||||
|
||||
var localID string
|
||||
{
|
||||
// Determine the local node ID
|
||||
localID = conf["node_id"]
|
||||
|
||||
// If not set in the config check the "node-id" file.
|
||||
if len(localID) == 0 {
|
||||
localIDRaw, err := ioutil.ReadFile(filepath.Join(path, "node-id"))
|
||||
switch {
|
||||
case err == nil:
|
||||
if len(localIDRaw) > 0 {
|
||||
localID = string(localIDRaw)
|
||||
}
|
||||
case os.IsNotExist(err):
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If the file didn't exist generate a UUID and persist it to tne
|
||||
// "node-id" file.
|
||||
if len(localID) == 0 {
|
||||
id, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(path, "node-id"), []byte(id), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localID = id
|
||||
}
|
||||
}
|
||||
|
||||
return &RaftBackend{
|
||||
logger: logger,
|
||||
fsm: fsm,
|
||||
conf: conf,
|
||||
logStore: log,
|
||||
stableStore: stable,
|
||||
snapStore: snap,
|
||||
dataDir: path,
|
||||
localID: localID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RaftServer has information about a server in the Raft configuration
|
||||
type RaftServer struct {
|
||||
// NodeID is the name of the server
|
||||
NodeID string `json:"node_id"`
|
||||
|
||||
// Address is the IP:port of the server, used for Raft communications
|
||||
Address string `json:"address"`
|
||||
|
||||
// Leader is true if this server is the current cluster leader
|
||||
Leader bool `json:"leader"`
|
||||
|
||||
// Protocol version is the raft protocol version used by the server
|
||||
ProtocolVersion string `json:"protocol_version"`
|
||||
|
||||
// Voter is true if this server has a vote in the cluster. This might
|
||||
// be false if the server is staging and still coming online.
|
||||
Voter bool `json:"voter"`
|
||||
}
|
||||
|
||||
// RaftConfigurationResponse is returned when querying for the current Raft
|
||||
// configuration.
|
||||
type RaftConfigurationResponse struct {
|
||||
// Servers has the list of servers in the Raft configuration.
|
||||
Servers []*RaftServer `json:"servers"`
|
||||
|
||||
// Index has the Raft index of this configuration.
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
// Peer defines the ID and Adress for a given member of the raft cluster.
|
||||
type Peer struct {
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// NodeID returns the identifier of the node
|
||||
func (b *RaftBackend) NodeID() string {
|
||||
return b.localID
|
||||
}
|
||||
|
||||
// Initialized tells if raft is running or not
|
||||
func (b *RaftBackend) Initialized() bool {
|
||||
b.l.RLock()
|
||||
init := b.raft != nil
|
||||
b.l.RUnlock()
|
||||
return init
|
||||
}
|
||||
|
||||
// SetTLSKeyring is used to install a new keyring. If the active key has changed
|
||||
// it will also close any network connections or streams forcing a reconnect
|
||||
// with the new key.
|
||||
func (b *RaftBackend) SetTLSKeyring(keyring *RaftTLSKeyring) error {
|
||||
b.l.RLock()
|
||||
err := b.streamLayer.setTLSKeyring(keyring)
|
||||
b.l.RUnlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetServerAddressProvider sets a the address provider for determining the raft
|
||||
// node addresses. This is currently only used in tests.
|
||||
func (b *RaftBackend) SetServerAddressProvider(provider raft.ServerAddressProvider) {
|
||||
b.l.Lock()
|
||||
b.serverAddressProvider = provider
|
||||
b.l.Unlock()
|
||||
}
|
||||
|
||||
// Bootstrap prepares the given peers to be part of the raft cluster
|
||||
func (b *RaftBackend) Bootstrap(ctx context.Context, peers []Peer) error {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
hasState, err := raft.HasExistingState(b.logStore, b.stableStore, b.snapStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasState {
|
||||
return errors.New("error bootstrapping cluster: cluster already has state")
|
||||
}
|
||||
|
||||
raftConfig := &raft.Configuration{
|
||||
Servers: make([]raft.Server, len(peers)),
|
||||
}
|
||||
|
||||
for i, p := range peers {
|
||||
raftConfig.Servers[i] = raft.Server{
|
||||
ID: raft.ServerID(p.ID),
|
||||
Address: raft.ServerAddress(p.Address),
|
||||
}
|
||||
}
|
||||
|
||||
// Store the config for later use
|
||||
b.bootstrapConfig = raftConfig
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRestoreCallback sets the callback to be used when a restoreCallbackOp is
|
||||
// processed through the FSM.
|
||||
func (b *RaftBackend) SetRestoreCallback(restoreCb restoreCallback) {
|
||||
b.fsm.l.Lock()
|
||||
b.fsm.restoreCb = restoreCb
|
||||
b.fsm.l.Unlock()
|
||||
}
|
||||
|
||||
func (b *RaftBackend) applyConfigSettings(config *raft.Config) error {
|
||||
config.Logger = b.logger
|
||||
multiplierRaw, ok := b.conf["performance_multiplier"]
|
||||
multiplier := 5
|
||||
if ok {
|
||||
var err error
|
||||
multiplier, err = strconv.Atoi(multiplierRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config.ElectionTimeout = config.ElectionTimeout * time.Duration(multiplier)
|
||||
config.HeartbeatTimeout = config.HeartbeatTimeout * time.Duration(multiplier)
|
||||
config.LeaderLeaseTimeout = config.LeaderLeaseTimeout * time.Duration(multiplier)
|
||||
|
||||
snapThresholdRaw, ok := b.conf["snapshot_threshold"]
|
||||
if ok {
|
||||
var err error
|
||||
snapThreshold, err := strconv.Atoi(snapThresholdRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.SnapshotThreshold = uint64(snapThreshold)
|
||||
}
|
||||
|
||||
trailingLogsRaw, ok := b.conf["trailing_logs"]
|
||||
if ok {
|
||||
var err error
|
||||
trailingLogs, err := strconv.Atoi(trailingLogsRaw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.TrailingLogs = uint64(trailingLogs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupCluster starts the raft cluster and enables the networking needed for
|
||||
// the raft nodes to communicate.
|
||||
func (b *RaftBackend) SetupCluster(ctx context.Context, raftTLSKeyring *RaftTLSKeyring, clusterListener cluster.ClusterHook) error {
|
||||
b.logger.Trace("setting up raft cluster")
|
||||
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
// We are already unsealed
|
||||
if b.raft != nil {
|
||||
b.logger.Debug("raft already started, not setting up cluster")
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(b.localID) == 0 {
|
||||
return errors.New("no local node id configured")
|
||||
}
|
||||
|
||||
// Setup the raft config
|
||||
raftConfig := raft.DefaultConfig()
|
||||
if err := b.applyConfigSettings(raftConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case raftTLSKeyring == nil && clusterListener == nil:
|
||||
// If we don't have a provided network we use an in-memory one.
|
||||
// This allows us to bootstrap a node without bringing up a cluster
|
||||
// network. This will be true during bootstrap and dev modes.
|
||||
_, b.raftTransport = raft.NewInmemTransport(raft.ServerAddress(b.localID))
|
||||
case raftTLSKeyring == nil:
|
||||
return errors.New("no keyring provided")
|
||||
case clusterListener == nil:
|
||||
return errors.New("no cluster listener provided")
|
||||
default:
|
||||
// Load the base TLS config from the cluster listener.
|
||||
baseTLSConfig, err := clusterListener.TLSConfig(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the local address and localID in the streaming layer and the raft config.
|
||||
streamLayer, err := NewRaftLayer(b.logger.Named("stream"), raftTLSKeyring, clusterListener.Addr(), baseTLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transConfig := &raft.NetworkTransportConfig{
|
||||
Stream: streamLayer,
|
||||
MaxPool: 3,
|
||||
Timeout: 10 * time.Second,
|
||||
ServerAddressProvider: b.serverAddressProvider,
|
||||
}
|
||||
transport := raft.NewNetworkTransportWithConfig(transConfig)
|
||||
|
||||
b.streamLayer = streamLayer
|
||||
b.raftTransport = transport
|
||||
}
|
||||
|
||||
raftConfig.LocalID = raft.ServerID(b.localID)
|
||||
|
||||
// Set up a channel for reliable leader notifications.
|
||||
raftNotifyCh := make(chan bool, 1)
|
||||
raftConfig.NotifyCh = raftNotifyCh
|
||||
|
||||
// If we have a bootstrapConfig set we should bootstrap now.
|
||||
if b.bootstrapConfig != nil {
|
||||
bootstrapConfig := b.bootstrapConfig
|
||||
// Unset the bootstrap config
|
||||
b.bootstrapConfig = nil
|
||||
|
||||
// Bootstrap raft with our known cluster members.
|
||||
if err := raft.BootstrapCluster(raftConfig, b.logStore, b.stableStore, b.snapStore, b.raftTransport, *bootstrapConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
// If we are the only node we should start as the leader.
|
||||
if len(bootstrapConfig.Servers) == 1 {
|
||||
raftConfig.StartAsLeader = true
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the Raft store.
|
||||
b.fsm.SetNoopRestore(true)
|
||||
|
||||
raftPath := filepath.Join(b.dataDir, raftState)
|
||||
peersFile := filepath.Join(raftPath, peersFileName)
|
||||
_, err := os.Stat(peersFile)
|
||||
if err == nil {
|
||||
b.logger.Info("raft recovery initiated", "recovery_file", peersFileName)
|
||||
|
||||
recoveryConfig, err := raft.ReadConfigJSON(peersFile)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("raft recovery failed to parse peers.json: {{err}}", err)
|
||||
}
|
||||
|
||||
b.logger.Info("raft recovery: found new config", "config", recoveryConfig)
|
||||
err = raft.RecoverCluster(raftConfig, b.fsm, b.logStore, b.stableStore, b.snapStore, b.raftTransport, recoveryConfig)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("raft recovery failed: {{err}}", err)
|
||||
}
|
||||
|
||||
err = os.Remove(peersFile)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("raft recovery failed to delete peers.json; please delete manually: {{err}}", err)
|
||||
}
|
||||
b.logger.Info("raft recovery deleted peers.json")
|
||||
}
|
||||
|
||||
raftObj, err := raft.NewRaft(raftConfig, b.fsm, b.logStore, b.stableStore, b.snapStore, b.raftTransport)
|
||||
b.fsm.SetNoopRestore(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.raft = raftObj
|
||||
b.raftNotifyCh = raftNotifyCh
|
||||
|
||||
if b.streamLayer != nil {
|
||||
// Add Handler to the cluster.
|
||||
clusterListener.AddHandler(consts.RaftStorageALPN, b.streamLayer)
|
||||
|
||||
// Add Client to the cluster.
|
||||
clusterListener.AddClient(consts.RaftStorageALPN, b.streamLayer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TeardownCluster shuts down the raft cluster
|
||||
func (b *RaftBackend) TeardownCluster(clusterListener cluster.ClusterHook) error {
|
||||
if clusterListener != nil {
|
||||
clusterListener.StopHandler(consts.RaftStorageALPN)
|
||||
clusterListener.RemoveClient(consts.RaftStorageALPN)
|
||||
}
|
||||
|
||||
b.l.Lock()
|
||||
future := b.raft.Shutdown()
|
||||
b.raft = nil
|
||||
b.l.Unlock()
|
||||
|
||||
return future.Error()
|
||||
}
|
||||
|
||||
// AppliedIndex returns the latest index applied to the FSM
|
||||
func (b *RaftBackend) AppliedIndex() uint64 {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return b.raft.AppliedIndex()
|
||||
}
|
||||
|
||||
// RemovePeer removes the given peer ID from the raft cluster. If the node is
|
||||
// ourselves we will give up leadership.
|
||||
func (b *RaftBackend) RemovePeer(ctx context.Context, peerID string) error {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return errors.New("raft storage is not initialized")
|
||||
}
|
||||
|
||||
future := b.raft.RemoveServer(raft.ServerID(peerID), 0, 0)
|
||||
|
||||
return future.Error()
|
||||
}
|
||||
|
||||
func (b *RaftBackend) GetConfiguration(ctx context.Context) (*RaftConfigurationResponse, error) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return nil, errors.New("raft storage is not initialized")
|
||||
}
|
||||
|
||||
future := b.raft.GetConfiguration()
|
||||
if err := future.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &RaftConfigurationResponse{
|
||||
Index: future.Index(),
|
||||
}
|
||||
|
||||
for _, server := range future.Configuration().Servers {
|
||||
entry := &RaftServer{
|
||||
NodeID: string(server.ID),
|
||||
Address: string(server.Address),
|
||||
Leader: server.Address == b.raft.Leader(),
|
||||
Voter: server.Suffrage == raft.Voter,
|
||||
ProtocolVersion: string(raft.ProtocolVersionMax),
|
||||
}
|
||||
config.Servers = append(config.Servers, entry)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// AddPeer adds a new server to the raft cluster
|
||||
func (b *RaftBackend) AddPeer(ctx context.Context, peerID, clusterAddr string) error {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return errors.New("raft storage is not initialized")
|
||||
}
|
||||
|
||||
future := b.raft.AddVoter(raft.ServerID(peerID), raft.ServerAddress(clusterAddr), 0, 0)
|
||||
|
||||
return future.Error()
|
||||
}
|
||||
|
||||
// Peers returns all the servers present in the raft cluster
|
||||
func (b *RaftBackend) Peers(ctx context.Context) ([]Peer, error) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return nil, errors.New("raft storage backend is not initialized")
|
||||
}
|
||||
|
||||
future := b.raft.GetConfiguration()
|
||||
if err := future.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]Peer, len(future.Configuration().Servers))
|
||||
for i, s := range future.Configuration().Servers {
|
||||
ret[i] = Peer{
|
||||
ID: string(s.ID),
|
||||
Address: string(s.Address),
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Snapshot takes a raft snapshot, packages it into a archive file and writes it
|
||||
// to the provided writer. Seal access is used to encrypt the SHASUM file so we
|
||||
// can validate the snapshot was taken using the same master keys or not.
|
||||
func (b *RaftBackend) Snapshot(out io.Writer, access seal.Access) error {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return errors.New("raft storage backend is sealed")
|
||||
}
|
||||
|
||||
// If we have access to the seal create a sealer object
|
||||
var s snapshot.Sealer
|
||||
if access != nil {
|
||||
s = &sealer{
|
||||
access: access,
|
||||
}
|
||||
}
|
||||
|
||||
snap, err := snapshot.NewWithSealer(b.logger.Named("snapshot"), b.raft, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer snap.Close()
|
||||
|
||||
_, err = io.Copy(out, snap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteSnapshotToTemp reads a snapshot archive off the provided reader,
|
||||
// extracts the data and writes the snapshot to a temporary file. The seal
|
||||
// access is used to decrypt the SHASUM file in the archive to ensure this
|
||||
// snapshot has the same master key as the running instance. If the provided
|
||||
// access is nil then it will skip that validation.
|
||||
func (b *RaftBackend) WriteSnapshotToTemp(in io.ReadCloser, access seal.Access) (*os.File, func(), raft.SnapshotMeta, error) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
var metadata raft.SnapshotMeta
|
||||
if b.raft == nil {
|
||||
return nil, nil, metadata, errors.New("raft storage backend is sealed")
|
||||
}
|
||||
|
||||
// If we have access to the seal create a sealer object
|
||||
var s snapshot.Sealer
|
||||
if access != nil {
|
||||
s = &sealer{
|
||||
access: access,
|
||||
}
|
||||
}
|
||||
|
||||
snap, cleanup, err := snapshot.WriteToTempFileWithSealer(b.logger.Named("snapshot"), in, &metadata, s)
|
||||
return snap, cleanup, metadata, err
|
||||
}
|
||||
|
||||
// RestoreSnapshot applies the provided snapshot metadata and snapshot data to
|
||||
// raft.
|
||||
func (b *RaftBackend) RestoreSnapshot(ctx context.Context, metadata raft.SnapshotMeta, snap io.Reader) error {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
if b.raft == nil {
|
||||
return errors.New("raft storage is not initialized")
|
||||
}
|
||||
|
||||
if err := b.raft.Restore(&metadata, snap, 0); err != nil {
|
||||
b.logger.Named("snapshot").Error("failed to restore snapshot", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply a log that tells the follower nodes to run the restore callback
|
||||
// function. This is done after the restore call so we can be sure the
|
||||
// snapshot applied to a quorum of nodes.
|
||||
command := &LogData{
|
||||
Operations: []*LogOperation{
|
||||
&LogOperation{
|
||||
OpType: restoreCallbackOp,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.l.RLock()
|
||||
err := b.applyLog(ctx, command)
|
||||
b.l.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete inserts an entry in the log to delete the given path
|
||||
func (b *RaftBackend) Delete(ctx context.Context, path string) error {
|
||||
command := &LogData{
|
||||
Operations: []*LogOperation{
|
||||
&LogOperation{
|
||||
OpType: deleteOp,
|
||||
Key: path,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.l.RLock()
|
||||
err := b.applyLog(ctx, command)
|
||||
b.l.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Get returns the value corresponding to the given path from the fsm
|
||||
func (b *RaftBackend) Get(ctx context.Context, path string) (*physical.Entry, error) {
|
||||
if b.fsm == nil {
|
||||
return nil, errors.New("raft: fsm not configured")
|
||||
}
|
||||
|
||||
return b.fsm.Get(ctx, path)
|
||||
}
|
||||
|
||||
// Put inserts an entry in the log for the put operation
|
||||
func (b *RaftBackend) Put(ctx context.Context, entry *physical.Entry) error {
|
||||
command := &LogData{
|
||||
Operations: []*LogOperation{
|
||||
&LogOperation{
|
||||
OpType: putOp,
|
||||
Key: entry.Key,
|
||||
Value: entry.Value,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.l.RLock()
|
||||
err := b.applyLog(ctx, command)
|
||||
b.l.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// List enumerates all the items under the prefix from the fsm
|
||||
func (b *RaftBackend) List(ctx context.Context, prefix string) ([]string, error) {
|
||||
if b.fsm == nil {
|
||||
return nil, errors.New("raft: fsm not configured")
|
||||
}
|
||||
|
||||
return b.fsm.List(ctx, prefix)
|
||||
}
|
||||
|
||||
// Transaction applies all the given operations into a single log and
|
||||
// applies it.
|
||||
func (b *RaftBackend) Transaction(ctx context.Context, txns []*physical.TxnEntry) error {
|
||||
command := &LogData{
|
||||
Operations: make([]*LogOperation, len(txns)),
|
||||
}
|
||||
for i, txn := range txns {
|
||||
op := &LogOperation{}
|
||||
switch txn.Operation {
|
||||
case physical.PutOperation:
|
||||
op.OpType = putOp
|
||||
op.Key = txn.Entry.Key
|
||||
op.Value = txn.Entry.Value
|
||||
case physical.DeleteOperation:
|
||||
op.OpType = deleteOp
|
||||
op.Key = txn.Entry.Key
|
||||
default:
|
||||
return fmt.Errorf("%q is not a supported transaction operation", txn.Operation)
|
||||
}
|
||||
|
||||
command.Operations[i] = op
|
||||
}
|
||||
|
||||
b.l.RLock()
|
||||
err := b.applyLog(ctx, command)
|
||||
b.l.RUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// applyLog will take a given log command and apply it to the raft log. applyLog
|
||||
// doesn't return until the log has been applied to a quorum of servers and is
|
||||
// persisted to the local FSM. Caller should hold the backend's read lock.
|
||||
func (b *RaftBackend) applyLog(ctx context.Context, command *LogData) error {
|
||||
if b.raft == nil {
|
||||
return errors.New("raft storage backend is not initialized")
|
||||
}
|
||||
|
||||
commandBytes, err := proto.Marshal(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restrict the value to maxCommandSizeBytes in length
|
||||
if len(commandBytes) > maxCommandSizeBytes {
|
||||
return ErrCommandTooLarge
|
||||
}
|
||||
|
||||
applyFuture := b.raft.Apply(commandBytes, 0)
|
||||
err = applyFuture.Error()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp, ok := applyFuture.Response().(*FSMApplyResponse); !ok || !resp.Success {
|
||||
return errors.New("could not apply data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HAEnabled is the implemention of the HABackend interface
|
||||
func (b *RaftBackend) HAEnabled() bool { return true }
|
||||
|
||||
// HAEnabled is the implemention of the HABackend interface
|
||||
func (b *RaftBackend) LockWith(key, value string) (physical.Lock, error) {
|
||||
return &RaftLock{
|
||||
key: key,
|
||||
value: []byte(value),
|
||||
b: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RaftLock implements the physical Lock interface and enables HA for this
|
||||
// backend. The Lock uses the raftNotifyCh for receiving leadership edge
|
||||
// triggers. Vault's active duty matches raft's leadership.
|
||||
type RaftLock struct {
|
||||
key string
|
||||
value []byte
|
||||
|
||||
b *RaftBackend
|
||||
}
|
||||
|
||||
// monitorLeadership waits until we receive an update on the raftNotifyCh and
|
||||
// closes the leaderLost channel.
|
||||
func (l *RaftLock) monitorLeadership(stopCh <-chan struct{}) <-chan struct{} {
|
||||
leaderLost := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-l.b.raftNotifyCh:
|
||||
close(leaderLost)
|
||||
case <-stopCh:
|
||||
}
|
||||
}()
|
||||
return leaderLost
|
||||
}
|
||||
|
||||
// Lock blocks until we become leader or are shutdown. It returns a channel that
|
||||
// is closed when we detect a loss of leadership.
|
||||
func (l *RaftLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
||||
for {
|
||||
select {
|
||||
case isLeader := <-l.b.raftNotifyCh:
|
||||
if isLeader {
|
||||
// We are leader, set the key
|
||||
l.b.l.RLock()
|
||||
err := l.b.applyLog(context.Background(), &LogData{
|
||||
Operations: []*LogOperation{
|
||||
&LogOperation{
|
||||
OpType: putOp,
|
||||
Key: l.key,
|
||||
Value: l.value,
|
||||
},
|
||||
},
|
||||
})
|
||||
l.b.l.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.monitorLeadership(stopCh), nil
|
||||
}
|
||||
case <-stopCh:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Unlock gives up leadership.
|
||||
func (l *RaftLock) Unlock() error {
|
||||
return l.b.raft.LeadershipTransfer().Error()
|
||||
}
|
||||
|
||||
// Value reads the value of the lock. This informs us who is currently leader.
|
||||
func (l *RaftLock) Value() (bool, string, error) {
|
||||
e, err := l.b.Get(context.Background(), l.key)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if e == nil {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
value := string(e.Value)
|
||||
// TODO: how to tell if held?
|
||||
return true, value, nil
|
||||
}
|
||||
|
||||
// sealer implements the snapshot.Sealer interface and is used in the snapshot
|
||||
// process for encrypting/decrypting the SHASUM file in snapshot archives.
|
||||
type sealer struct {
|
||||
access seal.Access
|
||||
}
|
||||
|
||||
// Seal encrypts the data with using the seal access object.
|
||||
func (s sealer) Seal(ctx context.Context, pt []byte) ([]byte, error) {
|
||||
if s.access == nil {
|
||||
return nil, errors.New("no seal access available")
|
||||
}
|
||||
eblob, err := s.access.Encrypt(ctx, pt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proto.Marshal(eblob)
|
||||
}
|
||||
|
||||
// Open decrypts the data using the seal access object.
|
||||
func (s sealer) Open(ctx context.Context, ct []byte) ([]byte, error) {
|
||||
if s.access == nil {
|
||||
return nil, errors.New("no seal access available")
|
||||
}
|
||||
|
||||
var eblob physical.EncryptedBlobInfo
|
||||
err := proto.Unmarshal(ct, &eblob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.access.Decrypt(ctx, &eblob)
|
||||
}
|
||||
454
physical/raft/raft_test.go
Normal file
454
physical/raft/raft_test.go
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
fmt "fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/golang/protobuf/proto"
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func getRaft(t testing.TB, bootstrap bool, noStoreState bool) (*RaftBackend, string) {
|
||||
raftDir, err := ioutil.TempDir("", "vault-raft-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("raft dir: %s", raftDir)
|
||||
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Name: "raft",
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
logger.Info("raft dir", "dir", raftDir)
|
||||
|
||||
conf := map[string]string{
|
||||
"path": raftDir,
|
||||
"trailing_logs": "100",
|
||||
}
|
||||
|
||||
if noStoreState {
|
||||
conf["doNotStoreLatestState"] = ""
|
||||
}
|
||||
|
||||
backendRaw, err := NewRaftBackend(conf, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backend := backendRaw.(*RaftBackend)
|
||||
|
||||
if bootstrap {
|
||||
err = backend.Bootstrap(context.Background(), []Peer{Peer{ID: backend.NodeID(), Address: backend.NodeID()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = backend.SetupCluster(context.Background(), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return backend, raftDir
|
||||
}
|
||||
|
||||
func compareFSMs(t *testing.T, fsm1, fsm2 *FSM) {
|
||||
t.Helper()
|
||||
index1, config1 := fsm1.LatestState()
|
||||
index2, config2 := fsm2.LatestState()
|
||||
|
||||
if !proto.Equal(index1, index2) {
|
||||
t.Fatalf("indexes did not match: %+v != %+v", index1, index2)
|
||||
}
|
||||
if !proto.Equal(config1, config2) {
|
||||
t.Fatalf("configs did not match: %+v != %+v", config1, config2)
|
||||
}
|
||||
|
||||
compareDBs(t, fsm1.db, fsm2.db)
|
||||
}
|
||||
|
||||
func compareDBs(t *testing.T, boltDB1, boltDB2 *bolt.DB) {
|
||||
db1 := make(map[string]string)
|
||||
db2 := make(map[string]string)
|
||||
|
||||
err := boltDB1.View(func(tx *bolt.Tx) error {
|
||||
|
||||
c := tx.Cursor()
|
||||
for bucketName, _ := c.First(); bucketName != nil; bucketName, _ = c.Next() {
|
||||
b := tx.Bucket(bucketName)
|
||||
|
||||
cBucket := b.Cursor()
|
||||
|
||||
for k, v := cBucket.First(); k != nil; k, v = cBucket.Next() {
|
||||
db1[string(k)] = base64.StdEncoding.EncodeToString(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = boltDB2.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Cursor()
|
||||
for bucketName, _ := c.First(); bucketName != nil; bucketName, _ = c.Next() {
|
||||
b := tx.Bucket(bucketName)
|
||||
|
||||
c := b.Cursor()
|
||||
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
db2[string(k)] = base64.StdEncoding.EncodeToString(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := deep.Equal(db1, db2); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_Backend(t *testing.T) {
|
||||
b, dir := getRaft(t, true, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
physical.ExerciseBackend(t, b)
|
||||
}
|
||||
|
||||
func TestRaft_Backend_ListPrefix(t *testing.T) {
|
||||
b, dir := getRaft(t, true, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
physical.ExerciseBackend_ListPrefix(t, b)
|
||||
}
|
||||
|
||||
func TestRaft_TransactionalBackend(t *testing.T) {
|
||||
b, dir := getRaft(t, true, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
physical.ExerciseTransactionalBackend(t, b)
|
||||
}
|
||||
|
||||
func TestRaft_HABackend(t *testing.T) {
|
||||
t.Skip()
|
||||
raft, dir := getRaft(t, true, true)
|
||||
defer os.RemoveAll(dir)
|
||||
raft2, dir2 := getRaft(t, false, true)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
// Add raft2 to the cluster
|
||||
addPeer(t, raft, raft2)
|
||||
|
||||
physical.ExerciseHABackend(t, raft, raft2)
|
||||
}
|
||||
|
||||
func TestRaft_Backend_ThreeNode(t *testing.T) {
|
||||
raft1, dir := getRaft(t, true, true)
|
||||
raft2, dir2 := getRaft(t, false, true)
|
||||
raft3, dir3 := getRaft(t, false, true)
|
||||
defer os.RemoveAll(dir)
|
||||
defer os.RemoveAll(dir2)
|
||||
defer os.RemoveAll(dir3)
|
||||
|
||||
// Add raft2 to the cluster
|
||||
addPeer(t, raft1, raft2)
|
||||
|
||||
// Add raft3 to the cluster
|
||||
addPeer(t, raft1, raft3)
|
||||
|
||||
physical.ExerciseBackend(t, raft1)
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
// Make sure all stores are the same
|
||||
compareFSMs(t, raft1.fsm, raft2.fsm)
|
||||
compareFSMs(t, raft1.fsm, raft3.fsm)
|
||||
}
|
||||
|
||||
func TestRaft_Recovery(t *testing.T) {
|
||||
// Create 4 raft nodes
|
||||
raft1, dir1 := getRaft(t, true, true)
|
||||
raft2, dir2 := getRaft(t, false, true)
|
||||
raft3, dir3 := getRaft(t, false, true)
|
||||
raft4, dir4 := getRaft(t, false, true)
|
||||
defer os.RemoveAll(dir1)
|
||||
defer os.RemoveAll(dir2)
|
||||
defer os.RemoveAll(dir3)
|
||||
defer os.RemoveAll(dir4)
|
||||
|
||||
// Add them all to the cluster
|
||||
addPeer(t, raft1, raft2)
|
||||
addPeer(t, raft1, raft3)
|
||||
addPeer(t, raft1, raft4)
|
||||
|
||||
// Add some data into the FSM
|
||||
physical.ExerciseBackend(t, raft1)
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Bring down all nodes
|
||||
raft1.TeardownCluster(nil)
|
||||
raft2.TeardownCluster(nil)
|
||||
raft3.TeardownCluster(nil)
|
||||
raft4.TeardownCluster(nil)
|
||||
|
||||
// Prepare peers.json
|
||||
type RecoveryPeer struct {
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
NonVoter bool `json: non_voter`
|
||||
}
|
||||
|
||||
// Leave out node 1 during recovery
|
||||
peersList := make([]*RecoveryPeer, 0, 3)
|
||||
peersList = append(peersList, &RecoveryPeer{
|
||||
ID: raft1.NodeID(),
|
||||
Address: raft1.NodeID(),
|
||||
NonVoter: false,
|
||||
})
|
||||
peersList = append(peersList, &RecoveryPeer{
|
||||
ID: raft2.NodeID(),
|
||||
Address: raft2.NodeID(),
|
||||
NonVoter: false,
|
||||
})
|
||||
peersList = append(peersList, &RecoveryPeer{
|
||||
ID: raft4.NodeID(),
|
||||
Address: raft4.NodeID(),
|
||||
NonVoter: false,
|
||||
})
|
||||
|
||||
peersJSONBytes, err := jsonutil.EncodeJSON(peersList)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(filepath.Join(dir1, raftState), "peers.json"), peersJSONBytes, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(filepath.Join(dir2, raftState), "peers.json"), peersJSONBytes, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(filepath.Join(dir4, raftState), "peers.json"), peersJSONBytes, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Bring up the nodes again
|
||||
raft1.SetupCluster(context.Background(), nil, nil)
|
||||
raft2.SetupCluster(context.Background(), nil, nil)
|
||||
raft4.SetupCluster(context.Background(), nil, nil)
|
||||
|
||||
peers, err := raft1.Peers(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(peers) != 3 {
|
||||
t.Fatalf("failed to recover the cluster")
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
compareFSMs(t, raft1.fsm, raft2.fsm)
|
||||
compareFSMs(t, raft1.fsm, raft4.fsm)
|
||||
}
|
||||
|
||||
func TestRaft_TransactionalBackend_ThreeNode(t *testing.T) {
|
||||
raft1, dir := getRaft(t, true, true)
|
||||
raft2, dir2 := getRaft(t, false, true)
|
||||
raft3, dir3 := getRaft(t, false, true)
|
||||
defer os.RemoveAll(dir)
|
||||
defer os.RemoveAll(dir2)
|
||||
defer os.RemoveAll(dir3)
|
||||
|
||||
// Add raft2 to the cluster
|
||||
addPeer(t, raft1, raft2)
|
||||
|
||||
// Add raft3 to the cluster
|
||||
addPeer(t, raft1, raft3)
|
||||
|
||||
physical.ExerciseTransactionalBackend(t, raft1)
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
// Make sure all stores are the same
|
||||
compareFSMs(t, raft1.fsm, raft2.fsm)
|
||||
compareFSMs(t, raft1.fsm, raft3.fsm)
|
||||
}
|
||||
|
||||
func TestRaft_Backend_MaxSize(t *testing.T) {
|
||||
// Set the max size a little lower for the test
|
||||
maxCommandSizeBytes = 10 * 1024
|
||||
|
||||
b, dir := getRaft(t, true, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Test a value slightly below the max size
|
||||
value := make([]byte, maxCommandSizeBytes-100)
|
||||
err := b.Put(context.Background(), &physical.Entry{
|
||||
Key: "key",
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test value at max size, should error
|
||||
value = make([]byte, maxCommandSizeBytes)
|
||||
err = b.Put(context.Background(), &physical.Entry{
|
||||
Key: "key",
|
||||
Value: value,
|
||||
})
|
||||
if err != ErrCommandTooLarge {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_Backend_Performance(t *testing.T) {
|
||||
b, dir := getRaft(t, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
defaultConfig := raft.DefaultConfig()
|
||||
|
||||
localConfig := raft.DefaultConfig()
|
||||
b.applyConfigSettings(localConfig)
|
||||
|
||||
if localConfig.ElectionTimeout != defaultConfig.ElectionTimeout*5 {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
if localConfig.HeartbeatTimeout != defaultConfig.HeartbeatTimeout*5 {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
if localConfig.LeaderLeaseTimeout != defaultConfig.LeaderLeaseTimeout*5 {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
|
||||
b.conf = map[string]string{
|
||||
"path": dir,
|
||||
"performance_multiplier": "5",
|
||||
}
|
||||
|
||||
localConfig = raft.DefaultConfig()
|
||||
b.applyConfigSettings(localConfig)
|
||||
|
||||
if localConfig.ElectionTimeout != defaultConfig.ElectionTimeout*5 {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
if localConfig.HeartbeatTimeout != defaultConfig.HeartbeatTimeout*5 {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
if localConfig.LeaderLeaseTimeout != defaultConfig.LeaderLeaseTimeout*5 {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
|
||||
b.conf = map[string]string{
|
||||
"path": dir,
|
||||
"performance_multiplier": "1",
|
||||
}
|
||||
|
||||
localConfig = raft.DefaultConfig()
|
||||
b.applyConfigSettings(localConfig)
|
||||
|
||||
if localConfig.ElectionTimeout != defaultConfig.ElectionTimeout {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
if localConfig.HeartbeatTimeout != defaultConfig.HeartbeatTimeout {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
if localConfig.LeaderLeaseTimeout != defaultConfig.LeaderLeaseTimeout {
|
||||
t.Fatalf("bad config: %v", localConfig)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkDB_Puts(b *testing.B) {
|
||||
raft, dir := getRaft(b, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
raft2, dir2 := getRaft(b, true, false)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
bench := func(b *testing.B, s physical.Backend, dataSize int) {
|
||||
data, err := uuid.GenerateRandomBytes(dataSize)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
pe := &physical.Entry{
|
||||
Value: data,
|
||||
}
|
||||
testName := b.Name()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pe.Key = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s-%d", testName, i))))
|
||||
err := s.Put(ctx, pe)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.Run("256b", func(b *testing.B) { bench(b, raft, 256) })
|
||||
b.Run("256kb", func(b *testing.B) { bench(b, raft2, 256*1024) })
|
||||
}
|
||||
|
||||
func BenchmarkDB_Snapshot(b *testing.B) {
|
||||
raft, dir := getRaft(b, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
data, err := uuid.GenerateRandomBytes(256 * 1024)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
pe := &physical.Entry{
|
||||
Value: data,
|
||||
}
|
||||
testName := b.Name()
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
pe.Key = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s-%d", testName, i))))
|
||||
err = raft.Put(ctx, pe)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
bench := func(b *testing.B, s *FSM) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pe.Key = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s-%d", testName, i))))
|
||||
s.writeTo(ctx, discardCloser{Writer: ioutil.Discard}, discardCloser{Writer: ioutil.Discard})
|
||||
}
|
||||
}
|
||||
|
||||
b.Run("256kb", func(b *testing.B) { bench(b, raft.fsm) })
|
||||
}
|
||||
|
||||
type discardCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (d discardCloser) Close() error { return nil }
|
||||
func (d discardCloser) CloseWithError(error) error { return nil }
|
||||
288
physical/raft/snapshot.go
Normal file
288
physical/raft/snapshot.go
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
|
||||
"github.com/hashicorp/raft"
|
||||
)
|
||||
|
||||
const (
|
||||
// boltSnapshotID is the stable ID for any boltDB snapshot. Keeping the ID
|
||||
// stable means there is only ever one bolt snapshot in the system
|
||||
boltSnapshotID = "bolt-snapshot"
|
||||
)
|
||||
|
||||
// BoltSnapshotStore implements the SnapshotStore interface and allows
|
||||
// snapshots to be made on the local disk. The main difference between this
|
||||
// store and the file store is we make the distinction between snapshots that
|
||||
// have been written by the FSM and by internal Raft operations. The former are
|
||||
// treated as noop snapshots on Persist and are read in full from the FSM on
|
||||
// Open. The latter are treated like normal file snapshots and are able to be
|
||||
// opened and applied as usual.
|
||||
type BoltSnapshotStore struct {
|
||||
// path is the directory in which to store file based snapshots
|
||||
path string
|
||||
// retain is the number of file based snapshots to keep
|
||||
retain int
|
||||
|
||||
// We hold a copy of the FSM so we can stream snapshots straight out of the
|
||||
// database.
|
||||
fsm *FSM
|
||||
|
||||
// fileSnapStore is used to fall back to file snapshots when the data is
|
||||
// being written from the raft library. This currently only happens on a
|
||||
// follower during a snapshot install RPC.
|
||||
fileSnapStore *raft.FileSnapshotStore
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// BoltSnapshotSink implements SnapshotSink optionally choosing to write to a
|
||||
// file.
|
||||
type BoltSnapshotSink struct {
|
||||
store *BoltSnapshotStore
|
||||
logger log.Logger
|
||||
meta raft.SnapshotMeta
|
||||
trans raft.Transport
|
||||
|
||||
fileSink raft.SnapshotSink
|
||||
l sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewBoltSnapshotStore creates a new BoltSnapshotStore based
|
||||
// on a base directory. The `retain` parameter controls how many
|
||||
// snapshots are retained. Must be at least 1.
|
||||
func NewBoltSnapshotStore(base string, retain int, logger log.Logger, fsm *FSM) (*BoltSnapshotStore, error) {
|
||||
if retain < 1 {
|
||||
return nil, fmt.Errorf("must retain at least one snapshot")
|
||||
}
|
||||
if logger == nil {
|
||||
return nil, fmt.Errorf("no logger provided")
|
||||
}
|
||||
|
||||
fileStore, err := raft.NewFileSnapshotStore(base, retain, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup the store
|
||||
store := &BoltSnapshotStore{
|
||||
logger: logger,
|
||||
fsm: fsm,
|
||||
fileSnapStore: fileStore,
|
||||
}
|
||||
|
||||
{
|
||||
// TODO: I think this needs to be done before every NewRaft and
|
||||
// RecoverCluster call. Not just on Factory method.
|
||||
|
||||
// Here we delete all the existing file based snapshots. This is necessary
|
||||
// because we do not issue a restore on NewRaft. If a previous file snapshot
|
||||
// had failed to apply we will be incorrectly setting the indexes. It's
|
||||
// safer to simply delete all file snapshots on startup and rely on Raft to
|
||||
// reconcile the FSM state.
|
||||
if err := store.ReapSnapshots(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// Create is used to start a new snapshot
|
||||
func (f *BoltSnapshotStore) Create(version raft.SnapshotVersion, index, term uint64,
|
||||
configuration raft.Configuration, configurationIndex uint64, trans raft.Transport) (raft.SnapshotSink, error) {
|
||||
// We only support version 1 snapshots at this time.
|
||||
if version != 1 {
|
||||
return nil, fmt.Errorf("unsupported snapshot version %d", version)
|
||||
}
|
||||
|
||||
// We are processing a snapshot, fastforward the index, term, and
|
||||
// configuration to the latest seen by the raft system. This could include
|
||||
// log indexes for operation types that are never sent to the FSM.
|
||||
if err := f.fsm.witnessSnapshot(index, term, configurationIndex, configuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the sink
|
||||
sink := &BoltSnapshotSink{
|
||||
store: f,
|
||||
logger: f.logger,
|
||||
meta: raft.SnapshotMeta{
|
||||
Version: version,
|
||||
ID: boltSnapshotID,
|
||||
Index: index,
|
||||
Term: term,
|
||||
Configuration: configuration,
|
||||
ConfigurationIndex: configurationIndex,
|
||||
},
|
||||
trans: trans,
|
||||
}
|
||||
|
||||
// Done
|
||||
return sink, nil
|
||||
}
|
||||
|
||||
// List returns available snapshots in the store. It only returns bolt
|
||||
// snapshots. No snapshot will be returned if there are no indexes in the
|
||||
// FSM.
|
||||
func (f *BoltSnapshotStore) List() ([]*raft.SnapshotMeta, error) {
|
||||
meta, err := f.getBoltSnapshotMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we haven't seen any data yet do not return a snapshot
|
||||
if meta.Index == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []*raft.SnapshotMeta{meta}, nil
|
||||
}
|
||||
|
||||
// getBoltSnapshotMeta returns the fsm's latest state and configuration.
|
||||
func (f *BoltSnapshotStore) getBoltSnapshotMeta() (*raft.SnapshotMeta, error) {
|
||||
latestIndex, latestConfig := f.fsm.LatestState()
|
||||
meta := &raft.SnapshotMeta{
|
||||
Version: 1,
|
||||
ID: boltSnapshotID,
|
||||
Index: latestIndex.Index,
|
||||
Term: latestIndex.Term,
|
||||
}
|
||||
|
||||
if latestConfig != nil {
|
||||
index, configuration := protoConfigurationToRaftConfiguration(latestConfig)
|
||||
meta.Configuration = configuration
|
||||
meta.ConfigurationIndex = index
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// Open takes a snapshot ID and returns a ReadCloser for that snapshot.
|
||||
func (f *BoltSnapshotStore) Open(id string) (*raft.SnapshotMeta, io.ReadCloser, error) {
|
||||
var readCloser io.ReadCloser
|
||||
var meta *raft.SnapshotMeta
|
||||
switch id {
|
||||
case boltSnapshotID:
|
||||
|
||||
var err error
|
||||
meta, err = f.getBoltSnapshotMeta()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// If we don't have any data return an error
|
||||
if meta.Index == 0 {
|
||||
return nil, nil, errors.New("no snapshot data")
|
||||
}
|
||||
|
||||
// Stream data out of the FSM to calculate the size
|
||||
var writeCloser *io.PipeWriter
|
||||
readCloser, writeCloser = io.Pipe()
|
||||
metaReadCloser, metaWriteCloser := io.Pipe()
|
||||
go func() {
|
||||
f.fsm.writeTo(context.Background(), metaWriteCloser, writeCloser)
|
||||
}()
|
||||
|
||||
// Compute the size
|
||||
n, err := io.Copy(ioutil.Discard, metaReadCloser)
|
||||
if err != nil {
|
||||
f.logger.Error("failed to read state file", "error", err)
|
||||
metaReadCloser.Close()
|
||||
readCloser.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
meta.Size = n
|
||||
|
||||
default:
|
||||
var err error
|
||||
meta, readCloser, err = f.fileSnapStore.Open(id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return meta, readCloser, nil
|
||||
}
|
||||
|
||||
// ReapSnapshots reaps any snapshots beyond the retain count.
|
||||
func (f *BoltSnapshotStore) ReapSnapshots() error {
|
||||
return f.fileSnapStore.ReapSnapshots()
|
||||
}
|
||||
|
||||
// ID returns the ID of the snapshot, can be used with Open()
|
||||
// after the snapshot is finalized.
|
||||
func (s *BoltSnapshotSink) ID() string {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
if s.fileSink != nil {
|
||||
return s.fileSink.ID()
|
||||
}
|
||||
|
||||
return s.meta.ID
|
||||
}
|
||||
|
||||
// Write is used to append to the state file. We write to the
|
||||
// buffered IO object to reduce the amount of context switches.
|
||||
func (s *BoltSnapshotSink) Write(b []byte) (int, error) {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
// If someone is writting to this sink then we need to create a file sink to
|
||||
// capture the data. This currently only happens when a follower is being
|
||||
// sent a snapshot.
|
||||
if s.fileSink == nil {
|
||||
fileSink, err := s.store.fileSnapStore.Create(s.meta.Version, s.meta.Index, s.meta.Term, s.meta.Configuration, s.meta.ConfigurationIndex, s.trans)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s.fileSink = fileSink
|
||||
}
|
||||
|
||||
return s.fileSink.Write(b)
|
||||
}
|
||||
|
||||
// Close is used to indicate a successful end.
|
||||
func (s *BoltSnapshotSink) Close() error {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
// Make sure close is idempotent
|
||||
if s.closed {
|
||||
return nil
|
||||
}
|
||||
s.closed = true
|
||||
|
||||
if s.fileSink != nil {
|
||||
return s.fileSink.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cancel is used to indicate an unsuccessful end.
|
||||
func (s *BoltSnapshotSink) Cancel() error {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
// Make sure close is idempotent
|
||||
if s.closed {
|
||||
return nil
|
||||
}
|
||||
s.closed = true
|
||||
|
||||
if s.fileSink != nil {
|
||||
return s.fileSink.Cancel()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
418
physical/raft/snapshot_test.go
Normal file
418
physical/raft/snapshot_test.go
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
fmt "fmt"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
)
|
||||
|
||||
type idAddr struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (a *idAddr) Network() string { return "inmem" }
|
||||
func (a *idAddr) String() string { return a.id }
|
||||
|
||||
func addPeer(t *testing.T, leader, follower *RaftBackend) {
|
||||
t.Helper()
|
||||
if err := leader.AddPeer(context.Background(), follower.NodeID(), follower.NodeID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
peers, err := leader.Peers(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = follower.Bootstrap(context.Background(), peers)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = follower.SetupCluster(context.Background(), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
leader.raftTransport.(*raft.InmemTransport).Connect(raft.ServerAddress(follower.NodeID()), follower.raftTransport)
|
||||
follower.raftTransport.(*raft.InmemTransport).Connect(raft.ServerAddress(leader.NodeID()), leader.raftTransport)
|
||||
}
|
||||
|
||||
func TestRaft_Snapshot_Loading(t *testing.T) {
|
||||
raft, dir := getRaft(t, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Write some data
|
||||
for i := 0; i < 1000; i++ {
|
||||
err := raft.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
readCloser, writeCloser := io.Pipe()
|
||||
metaReadCloser, metaWriteCloser := io.Pipe()
|
||||
|
||||
go func() {
|
||||
raft.fsm.writeTo(context.Background(), metaWriteCloser, writeCloser)
|
||||
}()
|
||||
|
||||
// Create a CRC64 hash
|
||||
stateHash := crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
|
||||
// Compute the hash
|
||||
size1, err := io.Copy(stateHash, metaReadCloser)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
computed1 := stateHash.Sum(nil)
|
||||
|
||||
// Create a CRC64 hash
|
||||
stateHash = crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
|
||||
// Compute the hash
|
||||
size2, err := io.Copy(stateHash, readCloser)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
computed2 := stateHash.Sum(nil)
|
||||
|
||||
if size1 != size2 {
|
||||
t.Fatal("sizes did not match")
|
||||
}
|
||||
|
||||
if !bytes.Equal(computed1, computed2) {
|
||||
t.Fatal("hashes did not match")
|
||||
}
|
||||
|
||||
snapFuture := raft.raft.Snapshot()
|
||||
if err := snapFuture.Error(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
meta, reader, err := snapFuture.Open()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if meta.Size != size1 {
|
||||
t.Fatal("meta size did not match expected")
|
||||
}
|
||||
|
||||
// Create a CRC64 hash
|
||||
stateHash = crc64.New(crc64.MakeTable(crc64.ECMA))
|
||||
|
||||
// Compute the hash
|
||||
size3, err := io.Copy(stateHash, reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
computed3 := stateHash.Sum(nil)
|
||||
if size1 != size3 {
|
||||
t.Fatal("sizes did not match")
|
||||
}
|
||||
|
||||
if !bytes.Equal(computed1, computed3) {
|
||||
t.Fatal("hashes did not match")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRaft_Snapshot_Index(t *testing.T) {
|
||||
raft, dir := getRaft(t, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
err := raft.Put(context.Background(), &physical.Entry{
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get index
|
||||
index, _ := raft.fsm.LatestState()
|
||||
if index.Term != 1 {
|
||||
t.Fatalf("unexpected term, got %d expected 1", index.Term)
|
||||
}
|
||||
if index.Index != 3 {
|
||||
t.Fatalf("unexpected index, got %d expected 3", index.Term)
|
||||
}
|
||||
|
||||
// Write some data
|
||||
for i := 0; i < 100; i++ {
|
||||
err := raft.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get index
|
||||
index, _ = raft.fsm.LatestState()
|
||||
if index.Term != 1 {
|
||||
t.Fatalf("unexpected term, got %d expected 1", index.Term)
|
||||
}
|
||||
if index.Index != 103 {
|
||||
t.Fatalf("unexpected index, got %d expected 103", index.Term)
|
||||
}
|
||||
|
||||
// Take a snapshot
|
||||
snapFuture := raft.raft.Snapshot()
|
||||
if err := snapFuture.Error(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
meta, reader, err := snapFuture.Open()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
io.Copy(ioutil.Discard, reader)
|
||||
|
||||
if meta.Index != index.Index {
|
||||
t.Fatalf("indexes did not match, got %d expected %d", meta.Index, index.Index)
|
||||
}
|
||||
if meta.Term != index.Term {
|
||||
t.Fatalf("term did not match, got %d expected %d", meta.Term, index.Term)
|
||||
}
|
||||
|
||||
// Write some more data
|
||||
for i := 0; i < 100; i++ {
|
||||
err := raft.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Open the same snapshot again
|
||||
meta, reader, err = raft.snapStore.Open(meta.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
io.Copy(ioutil.Discard, reader)
|
||||
|
||||
// Make sure the meta data has updated to the new values
|
||||
if meta.Index != 203 {
|
||||
t.Fatalf("unexpected snapshot index %d", meta.Index)
|
||||
}
|
||||
if meta.Term != 1 {
|
||||
t.Fatalf("unexpected snapshot term %d", meta.Term)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_Snapshot_Peers(t *testing.T) {
|
||||
raft1, dir := getRaft(t, true, false)
|
||||
raft2, dir2 := getRaft(t, false, false)
|
||||
raft3, dir3 := getRaft(t, false, false)
|
||||
defer os.RemoveAll(dir)
|
||||
defer os.RemoveAll(dir2)
|
||||
defer os.RemoveAll(dir3)
|
||||
|
||||
// Write some data
|
||||
for i := 0; i < 1000; i++ {
|
||||
err := raft1.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Force a snapshot
|
||||
snapFuture := raft1.raft.Snapshot()
|
||||
if err := snapFuture.Error(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add raft2 to the cluster
|
||||
addPeer(t, raft1, raft2)
|
||||
|
||||
// TODO: remove sleeps from these tests
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Make sure the snapshot was applied correctly on the follower
|
||||
compareDBs(t, raft1.fsm.db, raft2.fsm.db)
|
||||
|
||||
// Write some more data
|
||||
for i := 1000; i < 2000; i++ {
|
||||
err := raft1.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
snapFuture = raft1.raft.Snapshot()
|
||||
if err := snapFuture.Error(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Add raft3 to the cluster
|
||||
addPeer(t, raft1, raft3)
|
||||
|
||||
// TODO: remove sleeps from these tests
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Make sure all stores are the same
|
||||
compareFSMs(t, raft1.fsm, raft2.fsm)
|
||||
compareFSMs(t, raft1.fsm, raft3.fsm)
|
||||
}
|
||||
|
||||
func TestRaft_Snapshot_Restart(t *testing.T) {
|
||||
raft1, dir := getRaft(t, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
raft2, dir2 := getRaft(t, false, false)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
// Write some data
|
||||
for i := 0; i < 100; i++ {
|
||||
err := raft1.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Take a snapshot
|
||||
snapFuture := raft1.raft.Snapshot()
|
||||
if err := snapFuture.Error(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Advance FSM's index past configuration change
|
||||
raft1.Put(context.Background(), &physical.Entry{
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
})
|
||||
|
||||
// Add raft2 to the cluster
|
||||
addPeer(t, raft1, raft2)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
peers, err := raft2.Peers(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(peers) != 2 {
|
||||
t.Fatal(peers)
|
||||
}
|
||||
|
||||
// Shutdown raft1
|
||||
if err := raft1.TeardownCluster(nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Start Raft
|
||||
err = raft1.SetupCluster(context.Background(), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
peers, err = raft1.Peers(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(peers) != 2 {
|
||||
t.Fatal(peers)
|
||||
}
|
||||
|
||||
compareFSMs(t, raft1.fsm, raft2.fsm)
|
||||
}
|
||||
|
||||
func TestRaft_Snapshot_Take_Restore(t *testing.T) {
|
||||
raft1, dir := getRaft(t, true, false)
|
||||
defer os.RemoveAll(dir)
|
||||
raft2, dir2 := getRaft(t, false, false)
|
||||
defer os.RemoveAll(dir2)
|
||||
|
||||
addPeer(t, raft1, raft2)
|
||||
|
||||
// Write some data
|
||||
for i := 0; i < 100; i++ {
|
||||
err := raft1.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
snap := &bytes.Buffer{}
|
||||
|
||||
err := raft1.Snapshot(snap, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write some more data
|
||||
for i := 100; i < 200; i++ {
|
||||
err := raft1.Put(context.Background(), &physical.Entry{
|
||||
Key: fmt.Sprintf("key-%d", i),
|
||||
Value: []byte(fmt.Sprintf("value-%d", i)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
snapFile, cleanup, metadata, err := raft1.WriteSnapshotToTemp(ioutil.NopCloser(snap), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
err = raft1.RestoreSnapshot(context.Background(), metadata, snapFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure we don't have the second batch of writes
|
||||
for i := 100; i < 200; i++ {
|
||||
{
|
||||
value, err := raft1.Get(context.Background(), fmt.Sprintf("key-%d", i))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if value != nil {
|
||||
t.Fatal("didn't remove data")
|
||||
}
|
||||
}
|
||||
{
|
||||
value, err := raft2.Get(context.Background(), fmt.Sprintf("key-%d", i))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if value != nil {
|
||||
t.Fatal("didn't remove data")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
compareFSMs(t, raft1.fsm, raft2.fsm)
|
||||
}
|
||||
366
physical/raft/streamlayer.go
Normal file
366
physical/raft/streamlayer.go
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"errors"
|
||||
fmt "fmt"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/raft"
|
||||
"github.com/hashicorp/vault/sdk/helper/certutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/vault/cluster"
|
||||
)
|
||||
|
||||
// RaftTLSKey is a single TLS keypair in the Keyring
|
||||
type RaftTLSKey struct {
|
||||
// ID is a unique identifier for this Key
|
||||
ID string `json:"id"`
|
||||
|
||||
// KeyType defines the algorighm used to generate the private keys
|
||||
KeyType string `json:"key_type"`
|
||||
|
||||
// AppliedIndex is the earliest known raft index that safely contains this
|
||||
// key.
|
||||
AppliedIndex uint64 `json:"applied_index"`
|
||||
|
||||
// CertBytes is the marshaled certificate.
|
||||
CertBytes []byte `json:"cluster_cert"`
|
||||
|
||||
// KeyParams is the marshaled private key.
|
||||
KeyParams *certutil.ClusterKeyParams `json:"cluster_key_params"`
|
||||
|
||||
// CreatedTime is the time this key was generated. This value is useful in
|
||||
// determining when the next rotation should be.
|
||||
CreatedTime time.Time `json:"created_time"`
|
||||
|
||||
parsedCert *x509.Certificate
|
||||
parsedKey *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// RaftTLSKeyring is the set of keys that raft uses for network communication.
|
||||
// Only one key is used to dial at a time but both keys will be used to accept
|
||||
// connections.
|
||||
type RaftTLSKeyring struct {
|
||||
// Keys is the set of available key pairs
|
||||
Keys []*RaftTLSKey `json:"keys"`
|
||||
|
||||
// AppliedIndex is the earliest known raft index that safely contains the
|
||||
// latest key in the keyring.
|
||||
AppliedIndex uint64 `json:"applied_index"`
|
||||
|
||||
// Term is an incrementing identifier value used to quickly determine if two
|
||||
// states of the keyring are different.
|
||||
Term uint64 `json:"term"`
|
||||
|
||||
// ActiveKeyID is the key ID to track the active key in the keyring. Only
|
||||
// the active key is used for dialing.
|
||||
ActiveKeyID string `json:"active_key_id"`
|
||||
}
|
||||
|
||||
// GetActive returns the active key.
|
||||
func (k *RaftTLSKeyring) GetActive() *RaftTLSKey {
|
||||
if k.ActiveKeyID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, key := range k.Keys {
|
||||
if key.ID == k.ActiveKeyID {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateTLSKey() (*RaftTLSKey, error) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host = fmt.Sprintf("raft-%s", host)
|
||||
template := &x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: host,
|
||||
},
|
||||
DNSNames: []string{host},
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign,
|
||||
SerialNumber: big.NewInt(mathrand.Int63()),
|
||||
NotBefore: time.Now().Add(-30 * time.Second),
|
||||
// 30 years of single-active uptime ought to be enough for anybody
|
||||
NotAfter: time.Now().Add(262980 * time.Hour),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("unable to generate local cluster certificate: {{err}}", err)
|
||||
}
|
||||
|
||||
return &RaftTLSKey{
|
||||
ID: host,
|
||||
KeyType: certutil.PrivateKeyTypeP521,
|
||||
CertBytes: certBytes,
|
||||
KeyParams: &certutil.ClusterKeyParams{
|
||||
Type: certutil.PrivateKeyTypeP521,
|
||||
X: key.PublicKey.X,
|
||||
Y: key.PublicKey.Y,
|
||||
D: key.D,
|
||||
},
|
||||
CreatedTime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Make sure raftLayer satisfies the raft.StreamLayer interface
|
||||
var _ raft.StreamLayer = (*raftLayer)(nil)
|
||||
|
||||
// Make sure raftLayer satisfies the cluster.Handler and cluster.Client
|
||||
// interfaces
|
||||
var _ cluster.Handler = (*raftLayer)(nil)
|
||||
var _ cluster.Client = (*raftLayer)(nil)
|
||||
|
||||
// RaftLayer implements the raft.StreamLayer interface,
|
||||
// so that we can use a single RPC layer for Raft and Vault
|
||||
type raftLayer struct {
|
||||
// Addr is the listener address to return
|
||||
addr net.Addr
|
||||
|
||||
// connCh is used to accept connections
|
||||
connCh chan net.Conn
|
||||
|
||||
// Tracks if we are closed
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
closeLock sync.Mutex
|
||||
|
||||
logger log.Logger
|
||||
|
||||
dialerFunc func(string, time.Duration) (net.Conn, error)
|
||||
|
||||
// TLS config
|
||||
keyring *RaftTLSKeyring
|
||||
baseTLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// NewRaftLayer creates a new raftLayer object. It parses the TLS information
|
||||
// from the network config.
|
||||
func NewRaftLayer(logger log.Logger, raftTLSKeyring *RaftTLSKeyring, clusterAddr net.Addr, baseTLSConfig *tls.Config) (*raftLayer, error) {
|
||||
switch {
|
||||
case clusterAddr == nil:
|
||||
// Clustering disabled on the server, don't try to look for params
|
||||
return nil, errors.New("no raft addr found")
|
||||
}
|
||||
|
||||
layer := &raftLayer{
|
||||
addr: clusterAddr,
|
||||
connCh: make(chan net.Conn),
|
||||
closeCh: make(chan struct{}),
|
||||
logger: logger,
|
||||
baseTLSConfig: baseTLSConfig,
|
||||
}
|
||||
|
||||
if err := layer.setTLSKeyring(raftTLSKeyring); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
func (l *raftLayer) setTLSKeyring(keyring *RaftTLSKeyring) error {
|
||||
// Fast path a noop update
|
||||
if l.keyring != nil && l.keyring.Term == keyring.Term {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, key := range keyring.Keys {
|
||||
switch {
|
||||
case key.KeyParams == nil:
|
||||
return errors.New("no raft cluster key params found")
|
||||
|
||||
case key.KeyParams.X == nil, key.KeyParams.Y == nil, key.KeyParams.D == nil:
|
||||
return errors.New("failed to parse raft cluster key")
|
||||
|
||||
case key.KeyParams.Type != certutil.PrivateKeyTypeP521:
|
||||
return errors.New("failed to find valid raft cluster key type")
|
||||
|
||||
case len(key.CertBytes) == 0:
|
||||
return errors.New("no cluster cert found")
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(key.CertBytes)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error parsing raft cluster certificate: {{err}}", err)
|
||||
}
|
||||
|
||||
key.parsedCert = parsedCert
|
||||
key.parsedKey = &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: elliptic.P521(),
|
||||
X: key.KeyParams.X,
|
||||
Y: key.KeyParams.Y,
|
||||
},
|
||||
D: key.KeyParams.D,
|
||||
}
|
||||
}
|
||||
|
||||
if keyring.GetActive() == nil {
|
||||
return errors.New("expected one active key to be present in the keyring")
|
||||
}
|
||||
|
||||
l.keyring = keyring
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *raftLayer) ClientLookup(ctx context.Context, requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
for _, subj := range requestInfo.AcceptableCAs {
|
||||
for _, key := range l.keyring.Keys {
|
||||
if bytes.Equal(subj, key.parsedCert.RawIssuer) {
|
||||
localCert := make([]byte, len(key.CertBytes))
|
||||
copy(localCert, key.CertBytes)
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{localCert},
|
||||
PrivateKey: key.parsedKey,
|
||||
Leaf: key.parsedCert,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (l *raftLayer) ServerLookup(ctx context.Context, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if l.keyring == nil {
|
||||
return nil, errors.New("got raft connection but no local cert")
|
||||
}
|
||||
|
||||
for _, key := range l.keyring.Keys {
|
||||
if clientHello.ServerName == key.ID {
|
||||
localCert := make([]byte, len(key.CertBytes))
|
||||
copy(localCert, key.CertBytes)
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{localCert},
|
||||
PrivateKey: key.parsedKey,
|
||||
Leaf: key.parsedCert,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CALookup returns the CA to use when validating this connection.
|
||||
func (l *raftLayer) CALookup(context.Context) ([]*x509.Certificate, error) {
|
||||
ret := make([]*x509.Certificate, len(l.keyring.Keys))
|
||||
for i, key := range l.keyring.Keys {
|
||||
ret[i] = key.parsedCert
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Stop shutsdown the raft layer.
|
||||
func (l *raftLayer) Stop() error {
|
||||
l.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handoff is used to hand off a connection to the
|
||||
// RaftLayer. This allows it to be Accept()'ed
|
||||
func (l *raftLayer) Handoff(ctx context.Context, wg *sync.WaitGroup, quit chan struct{}, conn *tls.Conn) error {
|
||||
if l.closed {
|
||||
return errors.New("raft is shutdown")
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case l.connCh <- conn:
|
||||
case <-l.closeCh:
|
||||
case <-ctx.Done():
|
||||
case <-quit:
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Accept is used to return connection which are
|
||||
// dialed to be used with the Raft layer
|
||||
func (l *raftLayer) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-l.connCh:
|
||||
return conn, nil
|
||||
case <-l.closeCh:
|
||||
return nil, fmt.Errorf("Raft RPC layer closed")
|
||||
}
|
||||
}
|
||||
|
||||
// Close is used to stop listening for Raft connections
|
||||
func (l *raftLayer) Close() error {
|
||||
l.closeLock.Lock()
|
||||
defer l.closeLock.Unlock()
|
||||
|
||||
if !l.closed {
|
||||
l.closed = true
|
||||
close(l.closeCh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Addr is used to return the address of the listener
|
||||
func (l *raftLayer) Addr() net.Addr {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
// Dial is used to create a new outgoing connection
|
||||
func (l *raftLayer) Dial(address raft.ServerAddress, timeout time.Duration) (net.Conn, error) {
|
||||
|
||||
tlsConfig := l.baseTLSConfig.Clone()
|
||||
|
||||
key := l.keyring.GetActive()
|
||||
if key == nil {
|
||||
return nil, errors.New("no active key")
|
||||
}
|
||||
|
||||
tlsConfig.NextProtos = []string{consts.RaftStorageALPN}
|
||||
tlsConfig.ServerName = key.parsedCert.Subject.CommonName
|
||||
|
||||
l.logger.Debug("creating rpc dialer", "host", tlsConfig.ServerName)
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(key.parsedCert)
|
||||
tlsConfig.RootCAs = pool
|
||||
tlsConfig.ClientCAs = pool
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout,
|
||||
}
|
||||
return tls.DialWithDialer(dialer, "tcp", string(address), tlsConfig)
|
||||
}
|
||||
311
physical/raft/types.pb.go
Normal file
311
physical/raft/types.pb.go
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: physical/raft/types.proto
|
||||
|
||||
package raft
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type LogOperation struct {
|
||||
// OpType is the Operation type
|
||||
OpType uint32 `protobuf:"varint,1,opt,name=op_type,json=opType,proto3" json:"op_type,omitempty"`
|
||||
// Flags is an opaque value, currently unused. Reserved.
|
||||
Flags uint64 `protobuf:"varint,2,opt,name=flags,proto3" json:"flags,omitempty"`
|
||||
// Key that is being affected
|
||||
Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"`
|
||||
// Value is optional, corresponds to the key
|
||||
Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *LogOperation) Reset() { *m = LogOperation{} }
|
||||
func (m *LogOperation) String() string { return proto.CompactTextString(m) }
|
||||
func (*LogOperation) ProtoMessage() {}
|
||||
func (*LogOperation) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a8b3efb4def82ab3, []int{0}
|
||||
}
|
||||
|
||||
func (m *LogOperation) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_LogOperation.Unmarshal(m, b)
|
||||
}
|
||||
func (m *LogOperation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_LogOperation.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *LogOperation) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_LogOperation.Merge(m, src)
|
||||
}
|
||||
func (m *LogOperation) XXX_Size() int {
|
||||
return xxx_messageInfo_LogOperation.Size(m)
|
||||
}
|
||||
func (m *LogOperation) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_LogOperation.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_LogOperation proto.InternalMessageInfo
|
||||
|
||||
func (m *LogOperation) GetOpType() uint32 {
|
||||
if m != nil {
|
||||
return m.OpType
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *LogOperation) GetFlags() uint64 {
|
||||
if m != nil {
|
||||
return m.Flags
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *LogOperation) GetKey() string {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *LogOperation) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LogData struct {
|
||||
Operations []*LogOperation `protobuf:"bytes,1,rep,name=operations,proto3" json:"operations,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *LogData) Reset() { *m = LogData{} }
|
||||
func (m *LogData) String() string { return proto.CompactTextString(m) }
|
||||
func (*LogData) ProtoMessage() {}
|
||||
func (*LogData) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a8b3efb4def82ab3, []int{1}
|
||||
}
|
||||
|
||||
func (m *LogData) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_LogData.Unmarshal(m, b)
|
||||
}
|
||||
func (m *LogData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_LogData.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *LogData) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_LogData.Merge(m, src)
|
||||
}
|
||||
func (m *LogData) XXX_Size() int {
|
||||
return xxx_messageInfo_LogData.Size(m)
|
||||
}
|
||||
func (m *LogData) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_LogData.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_LogData proto.InternalMessageInfo
|
||||
|
||||
func (m *LogData) GetOperations() []*LogOperation {
|
||||
if m != nil {
|
||||
return m.Operations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type IndexValue struct {
|
||||
Term uint64 `protobuf:"varint,1,opt,name=term,proto3" json:"term,omitempty"`
|
||||
Index uint64 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *IndexValue) Reset() { *m = IndexValue{} }
|
||||
func (m *IndexValue) String() string { return proto.CompactTextString(m) }
|
||||
func (*IndexValue) ProtoMessage() {}
|
||||
func (*IndexValue) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a8b3efb4def82ab3, []int{2}
|
||||
}
|
||||
|
||||
func (m *IndexValue) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_IndexValue.Unmarshal(m, b)
|
||||
}
|
||||
func (m *IndexValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_IndexValue.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *IndexValue) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_IndexValue.Merge(m, src)
|
||||
}
|
||||
func (m *IndexValue) XXX_Size() int {
|
||||
return xxx_messageInfo_IndexValue.Size(m)
|
||||
}
|
||||
func (m *IndexValue) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_IndexValue.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_IndexValue proto.InternalMessageInfo
|
||||
|
||||
func (m *IndexValue) GetTerm() uint64 {
|
||||
if m != nil {
|
||||
return m.Term
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *IndexValue) GetIndex() uint64 {
|
||||
if m != nil {
|
||||
return m.Index
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Suffrage int32 `protobuf:"varint,1,opt,name=suffrage,proto3" json:"suffrage,omitempty"`
|
||||
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Server) Reset() { *m = Server{} }
|
||||
func (m *Server) String() string { return proto.CompactTextString(m) }
|
||||
func (*Server) ProtoMessage() {}
|
||||
func (*Server) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a8b3efb4def82ab3, []int{3}
|
||||
}
|
||||
|
||||
func (m *Server) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Server.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Server) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Server.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Server) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Server.Merge(m, src)
|
||||
}
|
||||
func (m *Server) XXX_Size() int {
|
||||
return xxx_messageInfo_Server.Size(m)
|
||||
}
|
||||
func (m *Server) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Server.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Server proto.InternalMessageInfo
|
||||
|
||||
func (m *Server) GetSuffrage() int32 {
|
||||
if m != nil {
|
||||
return m.Suffrage
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Server) GetId() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Server) GetAddress() string {
|
||||
if m != nil {
|
||||
return m.Address
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ConfigurationValue struct {
|
||||
Index uint64 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"`
|
||||
Servers []*Server `protobuf:"bytes,2,rep,name=servers,proto3" json:"servers,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ConfigurationValue) Reset() { *m = ConfigurationValue{} }
|
||||
func (m *ConfigurationValue) String() string { return proto.CompactTextString(m) }
|
||||
func (*ConfigurationValue) ProtoMessage() {}
|
||||
func (*ConfigurationValue) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_a8b3efb4def82ab3, []int{4}
|
||||
}
|
||||
|
||||
func (m *ConfigurationValue) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ConfigurationValue.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ConfigurationValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ConfigurationValue.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ConfigurationValue) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ConfigurationValue.Merge(m, src)
|
||||
}
|
||||
func (m *ConfigurationValue) XXX_Size() int {
|
||||
return xxx_messageInfo_ConfigurationValue.Size(m)
|
||||
}
|
||||
func (m *ConfigurationValue) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ConfigurationValue.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ConfigurationValue proto.InternalMessageInfo
|
||||
|
||||
func (m *ConfigurationValue) GetIndex() uint64 {
|
||||
if m != nil {
|
||||
return m.Index
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ConfigurationValue) GetServers() []*Server {
|
||||
if m != nil {
|
||||
return m.Servers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*LogOperation)(nil), "raft.LogOperation")
|
||||
proto.RegisterType((*LogData)(nil), "raft.LogData")
|
||||
proto.RegisterType((*IndexValue)(nil), "raft.IndexValue")
|
||||
proto.RegisterType((*Server)(nil), "raft.Server")
|
||||
proto.RegisterType((*ConfigurationValue)(nil), "raft.ConfigurationValue")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("physical/raft/types.proto", fileDescriptor_a8b3efb4def82ab3) }
|
||||
|
||||
var fileDescriptor_a8b3efb4def82ab3 = []byte{
|
||||
// 322 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xc1, 0x4b, 0xc3, 0x30,
|
||||
0x14, 0xc6, 0xc9, 0xd6, 0xad, 0xee, 0x39, 0x45, 0x1e, 0x82, 0xd5, 0x53, 0xe9, 0x41, 0x8a, 0x87,
|
||||
0x16, 0x26, 0x78, 0xf3, 0xa2, 0x5e, 0x84, 0xa1, 0x10, 0xc5, 0x83, 0x17, 0xc9, 0xd6, 0xb4, 0x0d,
|
||||
0x76, 0x4b, 0x48, 0xd2, 0x61, 0xff, 0x7b, 0x49, 0x63, 0xc7, 0xbc, 0xbd, 0xaf, 0xfd, 0x92, 0xef,
|
||||
0xf7, 0xe5, 0xc1, 0xa5, 0xaa, 0x3b, 0x23, 0xd6, 0xac, 0xc9, 0x35, 0x2b, 0x6d, 0x6e, 0x3b, 0xc5,
|
||||
0x4d, 0xa6, 0xb4, 0xb4, 0x12, 0x03, 0xf7, 0x25, 0xe1, 0x30, 0x5f, 0xca, 0xea, 0x55, 0x71, 0xcd,
|
||||
0xac, 0x90, 0x5b, 0xbc, 0x80, 0x50, 0xaa, 0x2f, 0xe7, 0x8b, 0x48, 0x4c, 0xd2, 0x13, 0x3a, 0x95,
|
||||
0xea, 0xbd, 0x53, 0x1c, 0xcf, 0x61, 0x52, 0x36, 0xac, 0x32, 0xd1, 0x28, 0x26, 0x69, 0x40, 0xbd,
|
||||
0xc0, 0x33, 0x18, 0x7f, 0xf3, 0x2e, 0x1a, 0xc7, 0x24, 0x9d, 0x51, 0x37, 0x3a, 0xdf, 0x8e, 0x35,
|
||||
0x2d, 0x8f, 0x82, 0x98, 0xa4, 0x73, 0xea, 0x45, 0x72, 0x0f, 0xe1, 0x52, 0x56, 0x4f, 0xcc, 0x32,
|
||||
0x5c, 0x00, 0xc8, 0x21, 0xce, 0x44, 0x24, 0x1e, 0xa7, 0xc7, 0x0b, 0xcc, 0x1c, 0x4c, 0x76, 0x48,
|
||||
0x42, 0x0f, 0x5c, 0xc9, 0x1d, 0xc0, 0xf3, 0xb6, 0xe0, 0x3f, 0x1f, 0xee, 0x32, 0x44, 0x08, 0x2c,
|
||||
0xd7, 0x9b, 0x1e, 0x30, 0xa0, 0xfd, 0xec, 0x62, 0x85, 0x73, 0x0c, 0x78, 0xbd, 0x48, 0x5e, 0x60,
|
||||
0xfa, 0xc6, 0xf5, 0x8e, 0x6b, 0xbc, 0x82, 0x23, 0xd3, 0x96, 0xa5, 0x66, 0x95, 0x2f, 0x36, 0xa1,
|
||||
0x7b, 0x8d, 0xa7, 0x30, 0x12, 0x45, 0x7f, 0x70, 0x46, 0x47, 0xa2, 0xc0, 0x08, 0x42, 0x56, 0x14,
|
||||
0x9a, 0x1b, 0xf3, 0x57, 0x6c, 0x90, 0x09, 0x05, 0x7c, 0x94, 0xdb, 0x52, 0x54, 0xad, 0x27, 0xf3,
|
||||
0x3c, 0xfb, 0x6c, 0x72, 0x90, 0x8d, 0xd7, 0x10, 0x9a, 0x3e, 0xdb, 0x3d, 0x99, 0x2b, 0x39, 0xf7,
|
||||
0x25, 0x3d, 0x10, 0x1d, 0x7e, 0x3e, 0xdc, 0x7c, 0xa6, 0x95, 0xb0, 0x75, 0xbb, 0xca, 0xd6, 0x72,
|
||||
0x93, 0xd7, 0xcc, 0xd4, 0x62, 0x2d, 0xb5, 0xca, 0x77, 0xac, 0x6d, 0x6c, 0xfe, 0x6f, 0x7f, 0xab,
|
||||
0x69, 0xbf, 0xba, 0xdb, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x85, 0xad, 0xad, 0xd7, 0x01,
|
||||
0x00, 0x00,
|
||||
}
|
||||
39
physical/raft/types.proto
Normal file
39
physical/raft/types.proto
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option go_package = "github.com/hashicorp/vault/physical/raft";
|
||||
|
||||
package raft;
|
||||
|
||||
message LogOperation {
|
||||
// OpType is the Operation type
|
||||
uint32 op_type = 1;
|
||||
|
||||
// Flags is an opaque value, currently unused. Reserved.
|
||||
uint64 flags = 2;
|
||||
|
||||
// Key that is being affected
|
||||
string key = 3;
|
||||
|
||||
// Value is optional, corresponds to the key
|
||||
bytes value = 4;
|
||||
}
|
||||
|
||||
message LogData {
|
||||
repeated LogOperation operations = 1;
|
||||
}
|
||||
|
||||
message IndexValue {
|
||||
uint64 term = 1;
|
||||
uint64 index = 2;
|
||||
}
|
||||
|
||||
message Server {
|
||||
int32 suffrage = 1;
|
||||
string id = 2;
|
||||
string address = 3;
|
||||
}
|
||||
|
||||
message ConfigurationValue {
|
||||
uint64 index = 1;
|
||||
repeated Server servers = 2;
|
||||
}
|
||||
|
|
@ -28,6 +28,10 @@ import (
|
|||
"github.com/hashicorp/vault/sdk/helper/errutil"
|
||||
)
|
||||
|
||||
const (
|
||||
PrivateKeyTypeP521 = "p521"
|
||||
)
|
||||
|
||||
// This can be one of a few key types so the different params may or may not be filled
|
||||
type ClusterKeyParams struct {
|
||||
Type string `json:"type" structs:"type" mapstructure:"type"`
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package logical
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -171,6 +172,14 @@ type Request struct {
|
|||
// ClientTokenSource tells us where the client token was sourced from, so
|
||||
// we can delete it before sending off to plugins
|
||||
ClientTokenSource ClientTokenSource
|
||||
|
||||
// RequestReader if set can be used to read the full request body from the
|
||||
// http request that generated this logical.Request object.
|
||||
RequestReader io.ReadCloser `json:"-" sentinel:""`
|
||||
|
||||
// ResponseWriter if set can be used to stream a response value to the http
|
||||
// request that generated this logical.Request object.
|
||||
ResponseWriter *HTTPResponseWriter `json:"-" sentinel:""`
|
||||
}
|
||||
|
||||
// Get returns a data field and guards for nil Data
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
||||
)
|
||||
|
|
@ -177,3 +179,31 @@ func RespondWithStatusCode(resp *Response, req *Request, code int) (*Response, e
|
|||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// HTTPResponseWriter is optionally added to a request object and can be used to
|
||||
// write directly to the HTTP response writter.
|
||||
type HTTPResponseWriter struct {
|
||||
writer io.Writer
|
||||
written *uint32
|
||||
}
|
||||
|
||||
// NewHTTPResponseWriter creates a new HTTPRepoinseWriter object that wraps the
|
||||
// provided io.Writer.
|
||||
func NewHTTPResponseWriter(w io.Writer) *HTTPResponseWriter {
|
||||
return &HTTPResponseWriter{
|
||||
writer: w,
|
||||
written: new(uint32),
|
||||
}
|
||||
}
|
||||
|
||||
// Write will write the bytes to the underlying io.Writer.
|
||||
func (rw *HTTPResponseWriter) Write(bytes []byte) (int, error) {
|
||||
atomic.StoreUint32(rw.written, 1)
|
||||
|
||||
return rw.writer.Write(bytes)
|
||||
}
|
||||
|
||||
// Written tells us if the writer has been written to yet.
|
||||
func (rw *HTTPResponseWriter) Written() bool {
|
||||
return atomic.LoadUint32(rw.written) == 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,6 +289,11 @@ func (c *Core) startClusterListener(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if c.clusterListener != nil {
|
||||
c.logger.Warn("cluster listener is already started")
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.clusterListenerAddrs == nil || len(c.clusterListenerAddrs) == 0 {
|
||||
c.logger.Warn("clustering not disabled but no addresses to listen on")
|
||||
return fmt.Errorf("cluster addresses not found")
|
||||
|
|
@ -309,6 +314,10 @@ func (c *Core) startClusterListener(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) ClusterAddr() string {
|
||||
return c.clusterAddr
|
||||
}
|
||||
|
||||
// stopClusterListener stops any existing listeners during seal. It is
|
||||
// assumed that the state lock is held while this is run.
|
||||
func (c *Core) stopClusterListener() {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ type Client interface {
|
|||
// off a connection for a cluster listener application.
|
||||
type Handler interface {
|
||||
ServerLookup(context.Context, *tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
CALookup(context.Context) (*x509.Certificate, error)
|
||||
CALookup(context.Context) ([]*x509.Certificate, error)
|
||||
|
||||
// Handoff is used to pass the connection lifetime off to
|
||||
// the handler
|
||||
|
|
@ -41,6 +41,15 @@ type Handler interface {
|
|||
Stop() error
|
||||
}
|
||||
|
||||
type ClusterHook interface {
|
||||
AddClient(alpn string, client Client)
|
||||
RemoveClient(alpn string)
|
||||
AddHandler(alpn string, handler Handler)
|
||||
StopHandler(alpn string)
|
||||
TLSConfig(ctx context.Context) (*tls.Config, error)
|
||||
Addr() net.Addr
|
||||
}
|
||||
|
||||
// Listener is the source of truth for cluster handlers and connection
|
||||
// clients. It dynamically builds the cluster TLS information. It's also
|
||||
// responsible for starting tcp listeners and accepting new cluster connections.
|
||||
|
|
@ -81,6 +90,11 @@ func NewListener(addrs []*net.TCPAddr, cipherSuites []uint16, logger log.Logger)
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: This probably isn't correct
|
||||
func (cl *Listener) Addr() net.Addr {
|
||||
return cl.listenerAddrs[0]
|
||||
}
|
||||
|
||||
func (cl *Listener) Addrs() []*net.TCPAddr {
|
||||
return cl.listenerAddrs
|
||||
}
|
||||
|
|
@ -181,12 +195,14 @@ func (cl *Listener) TLSConfig(ctx context.Context) (*tls.Config, error) {
|
|||
defer cl.l.RUnlock()
|
||||
for _, v := range clientHello.SupportedProtos {
|
||||
if handler, ok := cl.handlers[v]; ok {
|
||||
ca, err := handler.CALookup(ctx)
|
||||
caList, err := handler.CALookup(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caPool.AddCert(ca)
|
||||
for _, ca := range caList {
|
||||
caPool.AddCert(ca)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +222,7 @@ func (cl *Listener) TLSConfig(ctx context.Context) (*tls.Config, error) {
|
|||
}
|
||||
|
||||
// Run starts the tcp listeners and will accept connections until stop is
|
||||
// called.
|
||||
// called. This function blocks so should be called in a goroutine.
|
||||
func (cl *Listener) Run(ctx context.Context) error {
|
||||
// Get our TLS config
|
||||
tlsConfig, err := cl.TLSConfig(ctx)
|
||||
|
|
|
|||
121
vault/core.go
121
vault/core.go
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -15,7 +16,9 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/metricsutil"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
|
|
@ -34,12 +37,14 @@ import (
|
|||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/sdk/helper/mlock"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/tlsutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/shamir"
|
||||
"github.com/hashicorp/vault/vault/cluster"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -173,9 +178,21 @@ type Core struct {
|
|||
// physical backend is the un-trusted backend with durable data
|
||||
physical physical.Backend
|
||||
|
||||
// underlyingPhysical will always point to the underlying backend
|
||||
// implementation. This is an un-trusted backend with durable data
|
||||
underlyingPhysical physical.Backend
|
||||
|
||||
// seal is our seal, for seal configuration information
|
||||
seal Seal
|
||||
|
||||
raftUnseal bool
|
||||
|
||||
raftChallenge *physical.EncryptedBlobInfo
|
||||
|
||||
raftLeaderClient *api.Client
|
||||
|
||||
raftLeaderBarrierConfig *SealConfig
|
||||
|
||||
// migrationSeal is the seal to use during a migration operation. It is the
|
||||
// seal we're migrating *from*.
|
||||
migrationSeal Seal
|
||||
|
|
@ -353,6 +370,8 @@ type Core struct {
|
|||
rpcClientConn *grpc.ClientConn
|
||||
// The grpc forwarding client
|
||||
rpcForwardingClient *forwardingClient
|
||||
// The UUID used to hold the leader lock. Only set on active node
|
||||
leaderUUID string
|
||||
|
||||
// CORS Information
|
||||
corsConfig *CORSConfig
|
||||
|
|
@ -428,6 +447,11 @@ type Core struct {
|
|||
// Stores request counters
|
||||
counters counters
|
||||
|
||||
// Stores the raft applied index for standby nodes
|
||||
raftFollowerStates *raftFollowerStates
|
||||
// Stop channel for raft TLS rotations
|
||||
raftTLSRotationStopCh chan struct{}
|
||||
|
||||
coreNumber int
|
||||
}
|
||||
|
||||
|
|
@ -583,6 +607,7 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||
entCore: entCore{},
|
||||
devToken: conf.DevToken,
|
||||
physical: conf.Physical,
|
||||
underlyingPhysical: conf.Physical,
|
||||
redirectAddr: conf.RedirectAddr,
|
||||
clusterAddr: conf.ClusterAddr,
|
||||
seal: conf.Seal,
|
||||
|
|
@ -647,7 +672,7 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||
}
|
||||
|
||||
if c.seal == nil {
|
||||
c.seal = NewDefaultSeal()
|
||||
c.seal = NewDefaultSeal(shamirseal.NewSeal(c.logger.Named("shamir")))
|
||||
}
|
||||
c.seal.SetCore(c)
|
||||
|
||||
|
|
@ -824,7 +849,7 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !init {
|
||||
if !init && !c.isRaftUnseal() {
|
||||
return false, ErrNotInit
|
||||
}
|
||||
|
||||
|
|
@ -852,8 +877,65 @@ func (c *Core) unseal(key []byte, useRecoveryKeys bool) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if masterKey != nil {
|
||||
return c.unsealInternal(ctx, masterKey)
|
||||
if c.seal.BarrierType() == seal.Shamir {
|
||||
_, err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{
|
||||
"key": base64.StdEncoding.EncodeToString(masterKey),
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if !c.isRaftUnseal() {
|
||||
return c.unsealInternal(ctx, masterKey)
|
||||
}
|
||||
|
||||
// If we are in the middle of a raft join send the answer and wait for
|
||||
// data to start streaming in.
|
||||
if err := c.joinRaftSendAnswer(ctx, c.raftLeaderClient, c.raftChallenge, c.seal.GetAccess()); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Reset the state
|
||||
c.raftUnseal = false
|
||||
c.raftChallenge = nil
|
||||
c.raftLeaderBarrierConfig = nil
|
||||
c.raftLeaderClient = nil
|
||||
|
||||
go func() {
|
||||
keyringFound := false
|
||||
defer func() {
|
||||
if keyringFound {
|
||||
_, err := c.unsealInternal(ctx, masterKey)
|
||||
if err != nil {
|
||||
c.logger.Error("failed to unseal", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait until we at least have the keyring before we attempt to
|
||||
// unseal the node.
|
||||
for {
|
||||
keys, err := c.underlyingPhysical.List(ctx, keyringPrefix)
|
||||
if err != nil {
|
||||
c.logger.Error("failed to list physical keys", "error", err)
|
||||
return
|
||||
}
|
||||
if strutil.StrListContains(keys, "keyring") {
|
||||
keyringFound = true
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Return Vault as sealed since unsealing happens in background
|
||||
// which gets delayed until the data from the leader is streamed to
|
||||
// the follower.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
|
@ -884,9 +966,15 @@ func (c *Core) unsealPart(ctx context.Context, seal Seal, key []byte, useRecover
|
|||
|
||||
var config *SealConfig
|
||||
var err error
|
||||
if seal.RecoveryKeySupported() && (useRecoveryKeys || c.migrationSeal != nil) {
|
||||
|
||||
switch {
|
||||
case seal.RecoveryKeySupported() && (useRecoveryKeys || c.migrationSeal != nil):
|
||||
config, err = seal.RecoveryConfig(ctx)
|
||||
} else {
|
||||
case c.isRaftUnseal():
|
||||
// Ignore follower's seal config and refer to leader's barrier
|
||||
// configuration.
|
||||
config = c.raftLeaderBarrierConfig
|
||||
default:
|
||||
config, err = seal.BarrierConfig(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
@ -1076,9 +1164,6 @@ func (c *Core) unsealInternal(ctx context.Context, masterKey []byte) (bool, erro
|
|||
if err := c.barrier.Unseal(ctx, masterKey); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if c.logger.IsInfo() {
|
||||
c.logger.Info("vault is unsealed")
|
||||
}
|
||||
|
||||
if err := preUnsealInternal(ctx, c); err != nil {
|
||||
return false, err
|
||||
|
|
@ -1088,6 +1173,10 @@ func (c *Core) unsealInternal(ctx context.Context, masterKey []byte) (bool, erro
|
|||
return false, err
|
||||
}
|
||||
|
||||
if err := c.startRaftStorage(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Do post-unseal setup if HA is not enabled
|
||||
if c.ha == nil {
|
||||
// We still need to set up cluster info even if it's not part of a
|
||||
|
|
@ -1124,6 +1213,10 @@ func (c *Core) unsealInternal(ctx context.Context, masterKey []byte) (bool, erro
|
|||
// Success!
|
||||
atomic.StoreUint32(c.sealed, 0)
|
||||
|
||||
if c.logger.IsInfo() {
|
||||
c.logger.Info("vault is unsealed")
|
||||
}
|
||||
|
||||
if c.ha != nil {
|
||||
sd, ok := c.ha.(physical.ServiceDiscovery)
|
||||
if ok {
|
||||
|
|
@ -1409,6 +1502,14 @@ func (c *Core) sealInternalWithOptions(grabStateLock, keepHALock bool) error {
|
|||
c.logger.Debug("runStandby done")
|
||||
}
|
||||
|
||||
// If the storage backend needs to be sealed
|
||||
if raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend); ok {
|
||||
if err := raftStorage.TeardownCluster(c.clusterListener); err != nil {
|
||||
c.logger.Error("error stopping storage cluster", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the cluster listener
|
||||
c.stopClusterListener()
|
||||
|
||||
|
|
@ -1515,6 +1616,8 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c
|
|||
if err := c.startForwarding(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.startPeriodicRaftTLSRotate(ctx)
|
||||
}
|
||||
|
||||
c.clusterParamsLock.Lock()
|
||||
|
|
@ -1597,6 +1700,8 @@ func (c *Core) preSeal() error {
|
|||
}
|
||||
var result error
|
||||
|
||||
c.stopPeriodicRaftTLSRotate()
|
||||
|
||||
c.clusterParamsLock.Lock()
|
||||
if err := stopReplication(c); err != nil {
|
||||
result = multierror.Append(result, errwrap.Wrapf("error stopping replication: {{err}}", err))
|
||||
|
|
|
|||
716
vault/external_tests/raft/raft_test.go
Normal file
716
vault/external_tests/raft/raft_test.go
Normal file
|
|
@ -0,0 +1,716 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/testhelpers"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func TestRaft_ShamirUnseal(t *testing.T) {
|
||||
var cleanupFuncs []func()
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
Mutex: &sync.Mutex{},
|
||||
})
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Logger: logger,
|
||||
}
|
||||
i := 0
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
PhysicalFactory: func(logger hclog.Logger) (physical.Backend, error) {
|
||||
backend, cleanupFunc, err := testhelpers.CreateRaftBackend(t, logger, fmt.Sprintf("core-%d", i))
|
||||
i++
|
||||
cleanupFuncs = append(cleanupFuncs, cleanupFunc)
|
||||
return backend, err
|
||||
},
|
||||
Logger: logger,
|
||||
KeepStandbysSealed: true,
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
defer func() {
|
||||
for _, c := range cleanupFuncs {
|
||||
c()
|
||||
}
|
||||
}()
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
testhelpers.RaftClusterJoinNodes(t, cluster)
|
||||
|
||||
for i, c := range cluster.Cores {
|
||||
if c.Core.Sealed() {
|
||||
t.Fatalf("failed to unseal core %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_SnapshotAPI(t *testing.T) {
|
||||
var cleanupFuncs []func()
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
Mutex: &sync.Mutex{},
|
||||
})
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Logger: logger,
|
||||
}
|
||||
i := 0
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
PhysicalFactory: func(logger hclog.Logger) (physical.Backend, error) {
|
||||
backend, cleanupFunc, err := testhelpers.CreateRaftBackend(t, logger, fmt.Sprintf("core-%d", i))
|
||||
i++
|
||||
cleanupFuncs = append(cleanupFuncs, cleanupFunc)
|
||||
return backend, err
|
||||
},
|
||||
Logger: logger,
|
||||
KeepStandbysSealed: true,
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
defer func() {
|
||||
for _, c := range cleanupFuncs {
|
||||
c()
|
||||
}
|
||||
}()
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
testhelpers.RaftClusterJoinNodes(t, cluster)
|
||||
|
||||
leaderClient := cluster.Cores[0].Client
|
||||
|
||||
// Write a few keys
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
transport.TLSClientConfig = cluster.Cores[0].TLSConfig.Clone()
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
// Take a snapshot
|
||||
req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
|
||||
httpReq, err := req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
snap, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(snap) == 0 {
|
||||
t.Fatal("no snapshot returned")
|
||||
}
|
||||
|
||||
// Write a few more keys
|
||||
for i := 10; i < 20; i++ {
|
||||
_, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore snapshot
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
|
||||
req.Body = bytes.NewBuffer(snap)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// List kv to make sure we removed the extra keys
|
||||
secret, err := leaderClient.Logical().List("secret/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(secret.Data["keys"].([]interface{})) != 10 {
|
||||
t.Fatal("snapshot didn't apply correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_SnapshotAPI_RekeyRotate_Backward(t *testing.T) {
|
||||
tCases := []struct {
|
||||
Name string
|
||||
Rekey bool
|
||||
Rotate bool
|
||||
}{
|
||||
{
|
||||
Name: "rekey",
|
||||
Rekey: true,
|
||||
Rotate: false,
|
||||
},
|
||||
{
|
||||
Name: "rotate",
|
||||
Rekey: false,
|
||||
Rotate: true,
|
||||
},
|
||||
{
|
||||
Name: "both",
|
||||
Rekey: true,
|
||||
Rotate: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tCase := range tCases {
|
||||
t.Run(tCase.Name, func(t *testing.T) {
|
||||
// bind locally
|
||||
tCaseLocal := tCase
|
||||
t.Parallel()
|
||||
var cleanupFuncs []func()
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
Mutex: &sync.Mutex{},
|
||||
Name: tCaseLocal.Name,
|
||||
})
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Logger: logger,
|
||||
}
|
||||
i := 0
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
PhysicalFactory: func(logger hclog.Logger) (physical.Backend, error) {
|
||||
backend, cleanupFunc, err := testhelpers.CreateRaftBackend(t, logger, fmt.Sprintf("core-%d", i))
|
||||
i++
|
||||
cleanupFuncs = append(cleanupFuncs, cleanupFunc)
|
||||
return backend, err
|
||||
},
|
||||
Logger: logger,
|
||||
KeepStandbysSealed: true,
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
defer func() {
|
||||
for _, c := range cleanupFuncs {
|
||||
c()
|
||||
}
|
||||
}()
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
testhelpers.RaftClusterJoinNodes(t, cluster)
|
||||
|
||||
leaderClient := cluster.Cores[0].Client
|
||||
|
||||
// Write a few keys
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
transport.TLSClientConfig = cluster.Cores[0].TLSConfig.Clone()
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
// Take a snapshot
|
||||
req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
|
||||
httpReq, err := req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
snap, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(snap) == 0 {
|
||||
t.Fatal("no snapshot returned")
|
||||
}
|
||||
|
||||
// cache the original barrier keys
|
||||
barrierKeys := cluster.BarrierKeys
|
||||
|
||||
if tCaseLocal.Rotate {
|
||||
// Rotate
|
||||
err = leaderClient.Sys().Rotate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if tCaseLocal.Rekey {
|
||||
// Rekey
|
||||
testhelpers.RekeyCluster(t, cluster)
|
||||
}
|
||||
|
||||
if tCaseLocal.Rekey {
|
||||
// Restore snapshot, should fail.
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
|
||||
req.Body = bytes.NewBuffer(snap)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Parse Response
|
||||
apiResp := api.Response{Response: resp}
|
||||
if !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
|
||||
t.Fatal(apiResp.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Restore snapshot force
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
|
||||
req.Body = bytes.NewBuffer(snap)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testhelpers.EnsureStableActiveNode(t, cluster)
|
||||
|
||||
// Write some data so we can make sure we can read it later. This is testing
|
||||
// that we correctly reload the keyring
|
||||
_, err = leaderClient.Logical().Write("secret/foo", map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testhelpers.EnsureCoresSealed(t, cluster)
|
||||
|
||||
cluster.BarrierKeys = barrierKeys
|
||||
testhelpers.EnsureCoresUnsealed(t, cluster)
|
||||
testhelpers.WaitForActiveNode(t, cluster)
|
||||
activeCore := testhelpers.DeriveActiveCore(t, cluster)
|
||||
|
||||
// Read the value.
|
||||
data, err := activeCore.Client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data.Data["test"].(string) != "data" {
|
||||
t.Fatal(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_SnapshotAPI_RekeyRotate_Forward(t *testing.T) {
|
||||
tCases := []struct {
|
||||
Name string
|
||||
Rekey bool
|
||||
Rotate bool
|
||||
ShouldSeal bool
|
||||
}{
|
||||
{
|
||||
Name: "rekey",
|
||||
Rekey: true,
|
||||
Rotate: false,
|
||||
ShouldSeal: false,
|
||||
},
|
||||
{
|
||||
Name: "rotate",
|
||||
Rekey: false,
|
||||
Rotate: true,
|
||||
// Rotate writes a new master key upgrade using the new term, which
|
||||
// we can no longer decrypt. We must seal here.
|
||||
ShouldSeal: true,
|
||||
},
|
||||
{
|
||||
Name: "both",
|
||||
Rekey: true,
|
||||
Rotate: true,
|
||||
// If we are moving forward and we have rekeyed and rotated there
|
||||
// isn't any way to restore the latest keys so expect to seal.
|
||||
ShouldSeal: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tCase := range tCases {
|
||||
t.Run(tCase.Name, func(t *testing.T) {
|
||||
// bind locally
|
||||
tCaseLocal := tCase
|
||||
t.Parallel()
|
||||
var cleanupFuncs []func()
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
Mutex: &sync.Mutex{},
|
||||
Name: tCaseLocal.Name,
|
||||
})
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Logger: logger,
|
||||
}
|
||||
i := 0
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
PhysicalFactory: func(logger hclog.Logger) (physical.Backend, error) {
|
||||
backend, cleanupFunc, err := testhelpers.CreateRaftBackend(t, logger, fmt.Sprintf("core-%d", i))
|
||||
i++
|
||||
cleanupFuncs = append(cleanupFuncs, cleanupFunc)
|
||||
return backend, err
|
||||
},
|
||||
Logger: logger,
|
||||
KeepStandbysSealed: true,
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
defer func() {
|
||||
for _, c := range cleanupFuncs {
|
||||
c()
|
||||
}
|
||||
}()
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
testhelpers.RaftClusterJoinNodes(t, cluster)
|
||||
|
||||
leaderClient := cluster.Cores[0].Client
|
||||
|
||||
// Write a few keys
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
transport.TLSClientConfig = cluster.Cores[0].TLSConfig.Clone()
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
// Take a snapshot
|
||||
req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
|
||||
httpReq, err := req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
snap, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(snap) == 0 {
|
||||
t.Fatal("no snapshot returned")
|
||||
}
|
||||
|
||||
if tCaseLocal.Rekey {
|
||||
// Rekey
|
||||
testhelpers.RekeyCluster(t, cluster)
|
||||
}
|
||||
if tCaseLocal.Rotate {
|
||||
// Set the key clean up to 0 so it's cleaned immediately. This
|
||||
// will simulate that there are no ways to upgrade to the latest
|
||||
// term.
|
||||
vault.KeyRotateGracePeriod = 0
|
||||
|
||||
// Rotate
|
||||
err = leaderClient.Sys().Rotate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Let the key upgrade get deleted
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// cache the new barrier keys
|
||||
newBarrierKeys := cluster.BarrierKeys
|
||||
|
||||
// Take another snapshot for later use in "jumping" forward
|
||||
req = leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
snap2, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(snap2) == 0 {
|
||||
t.Fatal("no snapshot returned")
|
||||
}
|
||||
|
||||
// Restore snapshot to move us back in time so we can test going
|
||||
// forward
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
|
||||
req.Body = bytes.NewBuffer(snap)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testhelpers.EnsureStableActiveNode(t, cluster)
|
||||
if tCaseLocal.Rekey {
|
||||
// Restore snapshot, should fail.
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
|
||||
req.Body = bytes.NewBuffer(snap2)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Parse Response
|
||||
apiResp := api.Response{Response: resp}
|
||||
if !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
|
||||
t.Fatal(apiResp.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Restore snapshot force
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
|
||||
req.Body = bytes.NewBuffer(snap2)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch tCaseLocal.ShouldSeal {
|
||||
case true:
|
||||
testhelpers.WaitForNCoresSealed(t, cluster, 3)
|
||||
|
||||
case false:
|
||||
testhelpers.EnsureStableActiveNode(t, cluster)
|
||||
|
||||
// Write some data so we can make sure we can read it later. This is testing
|
||||
// that we correctly reload the keyring
|
||||
_, err = leaderClient.Logical().Write("secret/foo", map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testhelpers.EnsureCoresSealed(t, cluster)
|
||||
|
||||
cluster.BarrierKeys = newBarrierKeys
|
||||
testhelpers.EnsureCoresUnsealed(t, cluster)
|
||||
testhelpers.WaitForActiveNode(t, cluster)
|
||||
activeCore := testhelpers.DeriveActiveCore(t, cluster)
|
||||
|
||||
// Read the value.
|
||||
data, err := activeCore.Client.Logical().Read("secret/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data.Data["test"].(string) != "data" {
|
||||
t.Fatal(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaft_SnapshotAPI_DifferentCluster(t *testing.T) {
|
||||
|
||||
var cleanupFuncs []func()
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
Mutex: &sync.Mutex{},
|
||||
Name: "cluster1",
|
||||
})
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Logger: logger,
|
||||
}
|
||||
i := 0
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
PhysicalFactory: func(logger hclog.Logger) (physical.Backend, error) {
|
||||
backend, cleanupFunc, err := testhelpers.CreateRaftBackend(t, logger, fmt.Sprintf("core-%d", i))
|
||||
i++
|
||||
cleanupFuncs = append(cleanupFuncs, cleanupFunc)
|
||||
return backend, err
|
||||
},
|
||||
Logger: logger,
|
||||
KeepStandbysSealed: true,
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
defer func() {
|
||||
for _, c := range cleanupFuncs {
|
||||
c()
|
||||
}
|
||||
}()
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
testhelpers.RaftClusterJoinNodes(t, cluster)
|
||||
|
||||
leaderClient := cluster.Cores[0].Client
|
||||
|
||||
// Write a few keys
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := leaderClient.Logical().Write(fmt.Sprintf("secret/%d", i), map[string]interface{}{
|
||||
"test": "data",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
transport.TLSClientConfig = cluster.Cores[0].TLSConfig.Clone()
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
// Take a snapshot
|
||||
req := leaderClient.NewRequest("GET", "/v1/sys/storage/raft/snapshot")
|
||||
httpReq, err := req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
snap, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(snap) == 0 {
|
||||
t.Fatal("no snapshot returned")
|
||||
}
|
||||
|
||||
// Cluster 2
|
||||
{
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
Mutex: &sync.Mutex{},
|
||||
Name: "cluster2",
|
||||
})
|
||||
coreConfig := &vault.CoreConfig{
|
||||
Logger: logger,
|
||||
}
|
||||
i := 0
|
||||
cluster2 := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
PhysicalFactory: func(logger hclog.Logger) (physical.Backend, error) {
|
||||
backend, cleanupFunc, err := testhelpers.CreateRaftBackend(t, logger, fmt.Sprintf("core-%d", i))
|
||||
i++
|
||||
cleanupFuncs = append(cleanupFuncs, cleanupFunc)
|
||||
return backend, err
|
||||
},
|
||||
Logger: logger,
|
||||
KeepStandbysSealed: true,
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
cluster2.Start()
|
||||
defer cluster2.Cleanup()
|
||||
|
||||
testhelpers.RaftClusterJoinNodes(t, cluster2)
|
||||
|
||||
leaderClient := cluster2.Cores[0].Client
|
||||
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
transport.TLSClientConfig = cluster2.Cores[0].TLSConfig.Clone()
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
// Restore snapshot, should fail.
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot")
|
||||
req.Body = bytes.NewBuffer(snap)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Parse Response
|
||||
apiResp := api.Response{Response: resp}
|
||||
if !strings.Contains(apiResp.Error().Error(), "could not verify hash file, possibly the snapshot is using a different set of unseal keys") {
|
||||
t.Fatal(apiResp.Error())
|
||||
}
|
||||
|
||||
// Restore snapshot force
|
||||
req = leaderClient.NewRequest("POST", "/v1/sys/storage/raft/snapshot-force")
|
||||
req.Body = bytes.NewBuffer(snap)
|
||||
httpReq, err = req.ToHTTP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testhelpers.WaitForNCoresSealed(t, cluster2, 3)
|
||||
}
|
||||
}
|
||||
53
vault/ha.go
53
vault/ha.go
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
|
@ -19,6 +20,8 @@ import (
|
|||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
"github.com/oklog/run"
|
||||
)
|
||||
|
||||
|
|
@ -34,16 +37,16 @@ const (
|
|||
// rotation taking place.
|
||||
keyRotateCheckInterval = 10 * time.Second
|
||||
|
||||
// keyRotateGracePeriod is how long we allow an upgrade path
|
||||
// for standby instances before we delete the upgrade keys
|
||||
keyRotateGracePeriod = 2 * time.Minute
|
||||
|
||||
// leaderPrefixCleanDelay is how long to wait between deletions
|
||||
// of orphaned leader keys, to prevent slamming the backend.
|
||||
leaderPrefixCleanDelay = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyRotateGracePeriod is how long we allow an upgrade path
|
||||
// for standby instances before we delete the upgrade keys
|
||||
KeyRotateGracePeriod = 2 * time.Minute
|
||||
|
||||
addEnterpriseHaActors func(*Core, *run.Group) chan func() = addEnterpriseHaActorsNoop
|
||||
interruptPerfStandby func(chan func(), chan struct{}) chan struct{} = interruptPerfStandbyNoop
|
||||
)
|
||||
|
|
@ -338,11 +341,11 @@ func (c *Core) runStandby(doneCh, manualStepDownCh, stopCh chan struct{}) {
|
|||
}, func(error) {})
|
||||
}
|
||||
{
|
||||
// Monitor for key rotation
|
||||
// Monitor for key rotations
|
||||
keyRotateStop := make(chan struct{})
|
||||
|
||||
g.Add(func() error {
|
||||
c.periodicCheckKeyUpgrade(context.Background(), keyRotateStop)
|
||||
c.periodicCheckKeyUpgrades(context.Background(), keyRotateStop)
|
||||
return nil
|
||||
}, func(error) {
|
||||
close(keyRotateStop)
|
||||
|
|
@ -516,6 +519,7 @@ func (c *Core) waitForLeadership(newLeaderCh chan func(), manualStepDownCh, stop
|
|||
err = c.postUnseal(activeCtx, activeCtxCancel, standardUnsealStrategy{})
|
||||
if err == nil {
|
||||
c.standby = false
|
||||
c.leaderUUID = uuid
|
||||
}
|
||||
|
||||
close(continueCh)
|
||||
|
|
@ -562,6 +566,7 @@ func (c *Core) waitForLeadership(newLeaderCh chan func(), manualStepDownCh, stop
|
|||
|
||||
// Mark as standby
|
||||
c.standby = true
|
||||
c.leaderUUID = ""
|
||||
|
||||
// Seal
|
||||
if err := c.preSeal(); err != nil {
|
||||
|
|
@ -671,7 +676,7 @@ func (c *Core) periodicLeaderRefresh(newLeaderCh chan func(), stopCh chan struct
|
|||
}
|
||||
|
||||
// periodicCheckKeyUpgrade is used to watch for key rotation events as a standby
|
||||
func (c *Core) periodicCheckKeyUpgrade(ctx context.Context, stopCh chan struct{}) {
|
||||
func (c *Core) periodicCheckKeyUpgrades(ctx context.Context, stopCh chan struct{}) {
|
||||
opCount := new(int32)
|
||||
for {
|
||||
select {
|
||||
|
|
@ -710,6 +715,10 @@ func (c *Core) periodicCheckKeyUpgrade(ctx context.Context, stopCh chan struct{}
|
|||
c.logger.Error("key rotation periodic upgrade check failed", "error", err)
|
||||
}
|
||||
|
||||
if err := c.checkRaftTLSKeyUpgrades(ctx); err != nil {
|
||||
c.logger.Error("raft tls periodic upgrade check failed", "error", err)
|
||||
}
|
||||
|
||||
atomic.AddInt32(lopCount, -1)
|
||||
return
|
||||
}()
|
||||
|
|
@ -740,12 +749,34 @@ func (c *Core) checkKeyUpgrades(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) reloadMasterKey(ctx context.Context) error {
|
||||
if err := c.barrier.ReloadMasterKey(ctx); err != nil {
|
||||
return errwrap.Wrapf("error reloading master key: {{err}}", err)
|
||||
}
|
||||
|
||||
if c.seal.BarrierType() == seal.Shamir {
|
||||
keyring, err := c.barrier.Keyring()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to update seal access: {{err}}", err)
|
||||
}
|
||||
|
||||
_, err = c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{
|
||||
"key": base64.StdEncoding.EncodeToString(keyring.MasterKey()),
|
||||
})
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to update seal access: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) performKeyUpgrades(ctx context.Context) error {
|
||||
if err := c.checkKeyUpgrades(ctx); err != nil {
|
||||
return errwrap.Wrapf("error checking for key upgrades: {{err}}", err)
|
||||
}
|
||||
|
||||
if err := c.barrier.ReloadMasterKey(ctx); err != nil {
|
||||
if err := c.reloadMasterKey(ctx); err != nil {
|
||||
return errwrap.Wrapf("error reloading master key: {{err}}", err)
|
||||
}
|
||||
|
||||
|
|
@ -775,7 +806,7 @@ func (c *Core) scheduleUpgradeCleanup(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Schedule cleanup for all of them
|
||||
time.AfterFunc(keyRotateGracePeriod, func() {
|
||||
time.AfterFunc(KeyRotateGracePeriod, func() {
|
||||
sealed, err := c.barrier.Sealed()
|
||||
if err != nil {
|
||||
c.logger.Warn("failed to check barrier status at upgrade cleanup time")
|
||||
|
|
@ -816,7 +847,9 @@ func (c *Core) acquireLock(lock physical.Lock, stopCh <-chan struct{}) <-chan st
|
|||
|
||||
// advertiseLeader is used to advertise the current node as leader
|
||||
func (c *Core) advertiseLeader(ctx context.Context, uuid string, leaderLostCh <-chan struct{}) error {
|
||||
go c.cleanLeaderPrefix(ctx, uuid, leaderLostCh)
|
||||
if leaderLostCh != nil {
|
||||
go c.cleanLeaderPrefix(ctx, uuid, leaderLostCh)
|
||||
}
|
||||
|
||||
var key *ecdsa.PrivateKey
|
||||
switch c.localClusterPrivateKey.Load().(type) {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
|
|
@ -139,6 +144,32 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
|
|||
return nil, ErrAlreadyInit
|
||||
}
|
||||
|
||||
// If we have clustered storage, set it up now
|
||||
if raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend); ok {
|
||||
parsedClusterAddr, err := url.Parse(c.clusterAddr)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("error parsing cluster address: {{err}}", err)
|
||||
}
|
||||
if err := c.underlyingPhysical.(*raft.RaftBackend).Bootstrap(ctx, []raft.Peer{
|
||||
{
|
||||
ID: c.underlyingPhysical.(*raft.RaftBackend).NodeID(),
|
||||
Address: parsedClusterAddr.Host,
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, errwrap.Wrapf("could not bootstrap clustered storage: {{err}}", err)
|
||||
}
|
||||
|
||||
if err := raftStorage.SetupCluster(ctx, nil, nil); err != nil {
|
||||
return nil, errwrap.Wrapf("could not start clustered storage: {{err}}", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := raftStorage.TeardownCluster(nil); err != nil {
|
||||
c.logger.Error("failed to stop raft storage", "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
err = c.seal.Init(ctx)
|
||||
if err != nil {
|
||||
c.logger.Error("failed to initialize seal", "error", err)
|
||||
|
|
@ -266,6 +297,26 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
|
|||
results.RootToken = base64.StdEncoding.EncodeToString(encryptedVals[0])
|
||||
}
|
||||
|
||||
if _, ok := c.underlyingPhysical.(*raft.RaftBackend); ok {
|
||||
raftTLS, err := raft.GenerateTLSKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyring := &raft.RaftTLSKeyring{
|
||||
Keys: []*raft.RaftTLSKey{raftTLS},
|
||||
ActiveKeyID: raftTLS.ID,
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON(raftTLSStoragePath, keyring)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.barrier.Put(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare to re-seal
|
||||
if err := c.preSeal(); err != nil {
|
||||
c.logger.Error("pre-seal teardown failed", "error", err)
|
||||
|
|
@ -278,7 +329,7 @@ func (c *Core) Initialize(ctx context.Context, initParams *InitParams) (*InitRes
|
|||
// UnsealWithStoredKeys performs auto-unseal using stored keys. An error
|
||||
// return value of "nil" implies the Vault instance is unsealed.
|
||||
//
|
||||
// Callers should attempt to retry any NOnFatalErrors. Callers should
|
||||
// Callers should attempt to retry any NonFatalErrors. Callers should
|
||||
// not re-attempt fatal errors.
|
||||
func (c *Core) UnsealWithStoredKeys(ctx context.Context) error {
|
||||
c.unsealWithStoredKeysLock.Lock()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
memdb "github.com/hashicorp/go-memdb"
|
||||
|
|
@ -121,6 +123,8 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
|||
"replication/dr/secondary/operation-token/delete",
|
||||
"replication/dr/secondary/license",
|
||||
"replication/dr/secondary/reindex",
|
||||
"storage/raft/bootstrap/challenge",
|
||||
"storage/raft/bootstrap/answer",
|
||||
"init",
|
||||
"seal-status",
|
||||
"unseal",
|
||||
|
|
@ -198,6 +202,10 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
|||
})
|
||||
}
|
||||
|
||||
if _, ok := core.underlyingPhysical.(*raft.RaftBackend); ok {
|
||||
b.Backend.Paths = append(b.Backend.Paths, b.raftStoragePaths()...)
|
||||
}
|
||||
|
||||
b.Backend.Invalidate = sysInvalidate(b)
|
||||
return b
|
||||
}
|
||||
|
|
@ -2380,7 +2388,7 @@ func (b *SystemBackend) handleRotate(ctx context.Context, req *logical.Request,
|
|||
}
|
||||
|
||||
// Schedule the destroy of the upgrade path
|
||||
time.AfterFunc(keyRotateGracePeriod, func() {
|
||||
time.AfterFunc(KeyRotateGracePeriod, func() {
|
||||
if err := b.Core.barrier.DestroyUpgrade(ctx, newTerm); err != nil {
|
||||
b.Backend.Logger().Error("failed to destroy upgrade", "term", newTerm, "error", err)
|
||||
}
|
||||
|
|
|
|||
387
vault/logical_system_raft.go
Normal file
387
vault/logical_system_raft.go
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
|
||||
// raftStoragePaths returns paths for use when raft is the storage mechanism.
|
||||
func (b *SystemBackend) raftStoragePaths() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: "storage/raft/bootstrap/answer",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"server_id": {
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
"answer": {
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
"cluster_addr": {
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleRaftBootstrapAnswerWrite(),
|
||||
Summary: "Accepts an answer from the peer to be joined to the fact cluster.",
|
||||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysRaftHelp["raft-bootstrap-answer"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysRaftHelp["raft-bootstrap-answer"][1]),
|
||||
},
|
||||
{
|
||||
Pattern: "storage/raft/bootstrap/challenge",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"server_id": {
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleRaftBootstrapChallengeWrite(),
|
||||
Summary: "Creates a challenge for the new peer to be joined to the raft cluster.",
|
||||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysRaftHelp["raft-bootstrap-challenge"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysRaftHelp["raft-bootstrap-challenge"][1]),
|
||||
},
|
||||
{
|
||||
Pattern: "storage/raft/remove-peer",
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"server_id": {
|
||||
Type: framework.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleRaftRemovePeerUpdate(),
|
||||
Summary: "Remove a peer from the raft cluster.",
|
||||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][1]),
|
||||
},
|
||||
{
|
||||
Pattern: "storage/raft/configuration",
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.handleRaftConfigurationGet(),
|
||||
Summary: "Returns the configuration of the raft cluster.",
|
||||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][1]),
|
||||
},
|
||||
{
|
||||
Pattern: "storage/raft/snapshot",
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
Callback: b.handleStorageRaftSnapshotRead(),
|
||||
Summary: "Retruns a snapshot of the current state of vault.",
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleStorageRaftSnapshotWrite(false),
|
||||
Summary: "Installs the provided snapshot, returning the cluster to the state defined in it.",
|
||||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][1]),
|
||||
},
|
||||
{
|
||||
Pattern: "storage/raft/snapshot-force",
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handleStorageRaftSnapshotWrite(true),
|
||||
Summary: "Installs the provided snapshot, returning the cluster to the state defined in it. This bypasses checks ensuring the current Autounseal or Shamir keys are consistent with the snapshot data.",
|
||||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysRaftHelp["raft-remove-peer"][1]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var pendingRaftPeers = make(map[string][]byte)
|
||||
|
||||
func (b *SystemBackend) handleRaftConfigurationGet() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return logical.ErrorResponse("raft storage is not in use"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
config, err := raftStorage.GetConfiguration(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"config": config,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRaftRemovePeerUpdate() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
serverID := d.Get("server_id").(string)
|
||||
if len(serverID) == 0 {
|
||||
return logical.ErrorResponse("no server id provided"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return logical.ErrorResponse("raft storage is not in use"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
if err := raftStorage.RemovePeer(ctx, serverID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.Core.raftFollowerStates != nil {
|
||||
b.Core.raftFollowerStates.delete(serverID)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRaftBootstrapChallengeWrite() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
_, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return logical.ErrorResponse("raft storage is not in use"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
serverID := d.Get("server_id").(string)
|
||||
if len(serverID) == 0 {
|
||||
return logical.ErrorResponse("no server id provided"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
uuid, err := uuid.GenerateRandomBytes(16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sealAccess := b.Core.seal.GetAccess()
|
||||
eBlob, err := sealAccess.Encrypt(ctx, uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protoBlob, err := proto.Marshal(eBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pendingRaftPeers[serverID] = uuid
|
||||
sealConfig, err := b.Core.seal.BarrierConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"challenge": base64.StdEncoding.EncodeToString(protoBlob),
|
||||
"seal_config": sealConfig,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRaftBootstrapAnswerWrite() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return logical.ErrorResponse("raft storage is not in use"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
serverID := d.Get("server_id").(string)
|
||||
if len(serverID) == 0 {
|
||||
return logical.ErrorResponse("no server_id provided"), logical.ErrInvalidRequest
|
||||
}
|
||||
answerRaw := d.Get("answer").(string)
|
||||
if len(answerRaw) == 0 {
|
||||
return logical.ErrorResponse("no answer provided"), logical.ErrInvalidRequest
|
||||
}
|
||||
clusterAddr := d.Get("cluster_addr").(string)
|
||||
if len(clusterAddr) == 0 {
|
||||
return logical.ErrorResponse("no cluster_addr provided"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
answer, err := base64.StdEncoding.DecodeString(answerRaw)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("could not base64 decode answer"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
expectedAnswer, ok := pendingRaftPeers[serverID]
|
||||
if !ok {
|
||||
return logical.ErrorResponse("no expected answer for the server id provided"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
delete(pendingRaftPeers, serverID)
|
||||
|
||||
if subtle.ConstantTimeCompare(answer, expectedAnswer) == 0 {
|
||||
return logical.ErrorResponse("invalid answer given"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
tlsKeyringEntry, err := b.Core.barrier.Get(ctx, raftTLSStoragePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsKeyringEntry == nil {
|
||||
return nil, errors.New("could not find raft TLS configuration")
|
||||
}
|
||||
var keyring raft.RaftTLSKeyring
|
||||
if err := tlsKeyringEntry.DecodeJSON(&keyring); err != nil {
|
||||
return nil, errors.New("could not decode raft TLS configuration")
|
||||
}
|
||||
|
||||
if err := raftStorage.AddPeer(ctx, serverID, clusterAddr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.Core.raftFollowerStates != nil {
|
||||
b.Core.raftFollowerStates.update(serverID, 0)
|
||||
}
|
||||
|
||||
peers, err := raftStorage.Peers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"peers": peers,
|
||||
"tls_keyring": keyring,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleStorageRaftSnapshotRead() framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return logical.ErrorResponse("raft storage is not in use"), logical.ErrInvalidRequest
|
||||
}
|
||||
if req.ResponseWriter == nil {
|
||||
return nil, errors.New("no writer for request")
|
||||
}
|
||||
|
||||
err := raftStorage.Snapshot(req.ResponseWriter, b.Core.seal.GetAccess())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleStorageRaftSnapshotWrite(force bool) framework.OperationFunc {
|
||||
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
raftStorage, ok := b.Core.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return logical.ErrorResponse("raft storage is not in use"), logical.ErrInvalidRequest
|
||||
}
|
||||
if req.RequestReader == nil {
|
||||
return nil, errors.New("no reader for request")
|
||||
}
|
||||
|
||||
access := b.Core.seal.GetAccess()
|
||||
if force {
|
||||
access = nil
|
||||
}
|
||||
|
||||
// We want to buffer the http request reader into a temp file here so we
|
||||
// don't have to hold the full snapshot in memory. We also want to do
|
||||
// the restore in two parts so we can restore the snapshot while the
|
||||
// stateLock is write locked.
|
||||
snapFile, cleanup, metadata, err := raftStorage.WriteSnapshotToTemp(req.RequestReader, access)
|
||||
switch {
|
||||
case err == nil:
|
||||
case strings.Contains(err.Error(), "failed to open the sealed hashes"):
|
||||
switch b.Core.seal.BarrierType() {
|
||||
case seal.Shamir:
|
||||
return logical.ErrorResponse("could not verify hash file, possibly the snapshot is using a different set of unseal keys; use the snapshot-force API to bypass this check"), logical.ErrInvalidRequest
|
||||
default:
|
||||
return logical.ErrorResponse("could not verify hash file, possibly the snapshot is using a different autoseal key; use the snapshot-force API to bypass this check"), logical.ErrInvalidRequest
|
||||
}
|
||||
case err != nil:
|
||||
b.Core.logger.Error("raft snapshot restore: failed to write snapshot", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We want to do this in a go routine so we can upgrade the lock and
|
||||
// allow the client to disconnect.
|
||||
go func() {
|
||||
// Cleanup the temp file
|
||||
defer cleanup()
|
||||
|
||||
// Grab statelock
|
||||
if stopped := grabLockOrStop(b.Core.stateLock.Lock, b.Core.stateLock.Unlock, b.Core.standbyStopCh); stopped {
|
||||
b.Core.logger.Error("not applying snapshot; shutting down")
|
||||
return
|
||||
}
|
||||
defer b.Core.stateLock.Unlock()
|
||||
|
||||
// We are calling the callback function synchronously here while we
|
||||
// have the lock. So set it to nil and restore the callback when we
|
||||
// finish.
|
||||
raftStorage.SetRestoreCallback(nil)
|
||||
defer raftStorage.SetRestoreCallback(b.Core.raftSnapshotRestoreCallback(true))
|
||||
|
||||
b.Core.logger.Info("applying snapshot")
|
||||
if err := raftStorage.RestoreSnapshot(b.Core.activeContext, metadata, snapFile); err != nil {
|
||||
b.Core.logger.Error("error while restoring raft snapshot", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Run invalidation logic synchronously here
|
||||
callback := b.Core.raftSnapshotRestoreCallback(false)
|
||||
if err := callback(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
var sysRaftHelp = map[string][2]string{
|
||||
"raft-bootstrap-challenge": {
|
||||
"Creates a challenge for the new peer to be joined to the raft cluster.",
|
||||
"",
|
||||
},
|
||||
"raft-bootstrap-answer": {
|
||||
"Accepts an answer from the peer to be joined to the fact cluster.",
|
||||
"",
|
||||
},
|
||||
"raft-remove-peer": {
|
||||
"Removes a peer from the raft cluster.",
|
||||
"",
|
||||
},
|
||||
}
|
||||
674
vault/raft.go
Normal file
674
vault/raft.go
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
"github.com/hashicorp/errwrap"
|
||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var (
|
||||
raftTLSStoragePath = "core/raft/tls"
|
||||
raftTLSRotationPeriod = 24 * time.Hour
|
||||
)
|
||||
|
||||
type raftFollowerStates struct {
|
||||
l sync.RWMutex
|
||||
followers map[string]uint64
|
||||
}
|
||||
|
||||
func (s *raftFollowerStates) update(nodeID string, appliedIndex uint64) {
|
||||
s.l.Lock()
|
||||
s.followers[nodeID] = appliedIndex
|
||||
s.l.Unlock()
|
||||
}
|
||||
func (s *raftFollowerStates) delete(nodeID string) {
|
||||
s.l.RLock()
|
||||
delete(s.followers, nodeID)
|
||||
s.l.RUnlock()
|
||||
}
|
||||
func (s *raftFollowerStates) get(nodeID string) uint64 {
|
||||
s.l.RLock()
|
||||
index := s.followers[nodeID]
|
||||
s.l.RUnlock()
|
||||
return index
|
||||
}
|
||||
func (s *raftFollowerStates) minIndex() uint64 {
|
||||
var min uint64 = math.MaxUint64
|
||||
minFunc := func(a, b uint64) uint64 {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
s.l.RLock()
|
||||
for _, i := range s.followers {
|
||||
min = minFunc(min, i)
|
||||
}
|
||||
s.l.RUnlock()
|
||||
|
||||
if min == math.MaxUint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return min
|
||||
}
|
||||
|
||||
// startRaftStorage will call SetupCluster in the raft backend which starts raft
|
||||
// up and enables the cluster handler.
|
||||
func (c *Core) startRaftStorage(ctx context.Context) error {
|
||||
raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if raftStorage.Initialized() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve the raft TLS information
|
||||
raftTLSEntry, err := c.barrier.Get(ctx, raftTLSStoragePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if raftTLSEntry == nil {
|
||||
return errors.New("could not find raft TLS configuration")
|
||||
}
|
||||
|
||||
raftTLS := new(raft.RaftTLSKeyring)
|
||||
if err := raftTLSEntry.DecodeJSON(raftTLS); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raftStorage.SetRestoreCallback(c.raftSnapshotRestoreCallback(true))
|
||||
if err := raftStorage.SetupCluster(ctx, raftTLS, c.clusterListener); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startPeriodicRaftTLSRotate will spawn a go routine in charge of periodically
|
||||
// rotating the TLS certs and keys used for raft traffic.
|
||||
//
|
||||
// The logic for updating the TLS certificate uses a pseudo two phase commit
|
||||
// using the known applied indexes from standby nodes. When writing a new Key
|
||||
// it will be appended to the end of the keyring. Standbys can start accepting
|
||||
// connections with this key as soon as they see the update. Then it will write
|
||||
// the keyring a second time indicating the applied index for this key update.
|
||||
//
|
||||
// The active node will wait until it sees all standby nodes are at or past the
|
||||
// applied index for this update. At that point it will delete the older key
|
||||
// and make the new key active. The key isn't officially in use until this
|
||||
// happens. The dual write ensures the standby at least gets the first update
|
||||
// containing the key before the active node switches over to using it.
|
||||
//
|
||||
// If a standby is shut down then it cannot advance the key term until it
|
||||
// receives the update. This ensures a standby node isn't left behind and unable
|
||||
// to reconnect with the cluster. Additionally, only one outstanding key
|
||||
// is allowed for this same reason (max keyring size of 2).
|
||||
func (c *Core) startPeriodicRaftTLSRotate(ctx context.Context) error {
|
||||
raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
followerStates := &raftFollowerStates{
|
||||
followers: make(map[string]uint64),
|
||||
}
|
||||
|
||||
// Pre-populate the follower list with the set of peers.
|
||||
raftConfig, err := raftStorage.GetConfiguration(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, server := range raftConfig.Servers {
|
||||
if !server.Leader {
|
||||
followerStates.update(server.NodeID, 0)
|
||||
}
|
||||
}
|
||||
|
||||
logger := c.logger.Named("raft")
|
||||
c.raftTLSRotationStopCh = stopCh
|
||||
c.raftFollowerStates = followerStates
|
||||
|
||||
readKeyring := func() (*raft.RaftTLSKeyring, error) {
|
||||
tlsKeyringEntry, err := c.barrier.Get(ctx, raftTLSStoragePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsKeyringEntry == nil {
|
||||
return nil, errors.New("no keyring found")
|
||||
}
|
||||
var keyring raft.RaftTLSKeyring
|
||||
if err := tlsKeyringEntry.DecodeJSON(&keyring); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &keyring, nil
|
||||
}
|
||||
|
||||
// rotateKeyring writes new key data to the keyring and adds an applied
|
||||
// index that is used to verify it has been committed. The keys written in
|
||||
// this function can be used on standbys but the active node doesn't start
|
||||
// using it yet.
|
||||
rotateKeyring := func() (time.Time, error) {
|
||||
// Read the existing keyring
|
||||
keyring, err := readKeyring()
|
||||
if err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to read raft TLS keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(keyring.Keys) == 2 && keyring.Keys[1].AppliedIndex == 0:
|
||||
// If this case is hit then the second write to add the applied
|
||||
// index failed. Attempt to write it again.
|
||||
keyring.Keys[1].AppliedIndex = raftStorage.AppliedIndex()
|
||||
keyring.AppliedIndex = raftStorage.AppliedIndex()
|
||||
entry, err := logical.StorageEntryJSON(raftTLSStoragePath, keyring)
|
||||
if err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to json encode keyring: {{err}}", err)
|
||||
}
|
||||
if err := c.barrier.Put(ctx, entry); err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to write keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
case len(keyring.Keys) > 1:
|
||||
// If there already exists a pending key update then the update
|
||||
// hasn't replicated down to all standby nodes yet. Don't allow any
|
||||
// new keys to be created until all standbys have seen this previous
|
||||
// rotation. As a backoff strategy another rotation attempt is
|
||||
// scheduled for 5 minutes from now.
|
||||
logger.Warn("skipping new raft TLS config creation, keys are pending")
|
||||
return time.Now().Add(time.Minute * 5), nil
|
||||
}
|
||||
|
||||
logger.Info("creating new raft TLS config")
|
||||
|
||||
// Create a new key
|
||||
raftTLSKey, err := raft.GenerateTLSKey()
|
||||
if err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to generate new raft TLS key: {{err}}", err)
|
||||
}
|
||||
|
||||
// Advance the term and store the new key
|
||||
keyring.Term += 1
|
||||
keyring.Keys = append(keyring.Keys, raftTLSKey)
|
||||
entry, err := logical.StorageEntryJSON(raftTLSStoragePath, keyring)
|
||||
if err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to json encode keyring: {{err}}", err)
|
||||
}
|
||||
if err := c.barrier.Put(ctx, entry); err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to write keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
// Write the keyring again with the new applied index. This allows us to
|
||||
// track if standby nodes receive the update.
|
||||
keyring.Keys[1].AppliedIndex = raftStorage.AppliedIndex()
|
||||
keyring.AppliedIndex = raftStorage.AppliedIndex()
|
||||
entry, err = logical.StorageEntryJSON(raftTLSStoragePath, keyring)
|
||||
if err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to json encode keyring: {{err}}", err)
|
||||
}
|
||||
if err := c.barrier.Put(ctx, entry); err != nil {
|
||||
return time.Time{}, errwrap.Wrapf("failed to write keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
logger.Info("wrote new raft TLS config")
|
||||
// Schedule the next rotation
|
||||
return raftTLSKey.CreatedTime.Add(raftTLSRotationPeriod), nil
|
||||
}
|
||||
|
||||
// checkCommitted verifies key updates have been applied to all nodes and
|
||||
// finalizes the rotation by deleting the old keys and updating the raft
|
||||
// backend.
|
||||
checkCommitted := func() error {
|
||||
keyring, err := readKeyring()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to read raft TLS keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(keyring.Keys) == 1:
|
||||
// No Keys to apply
|
||||
return nil
|
||||
case keyring.Keys[1].AppliedIndex != keyring.AppliedIndex:
|
||||
// We haven't fully committed the new key, continue here
|
||||
return nil
|
||||
case followerStates.minIndex() < keyring.AppliedIndex:
|
||||
// Not all the followers have applied the latest key
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upgrade to the new key
|
||||
keyring.Keys = keyring.Keys[1:]
|
||||
keyring.ActiveKeyID = keyring.Keys[0].ID
|
||||
keyring.Term += 1
|
||||
entry, err := logical.StorageEntryJSON(raftTLSStoragePath, keyring)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to json encode keyring: {{err}}", err)
|
||||
}
|
||||
if err := c.barrier.Put(ctx, entry); err != nil {
|
||||
return errwrap.Wrapf("failed to write keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
// Update the TLS Key in the backend
|
||||
if err := raftStorage.SetTLSKeyring(keyring); err != nil {
|
||||
return errwrap.Wrapf("failed to install keyring: {{err}}", err)
|
||||
}
|
||||
|
||||
logger.Info("installed new raft TLS key", "term", keyring.Term)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the keyring to calculate the time of next rotation.
|
||||
keyring, err := readKeyring()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
activeKey := keyring.GetActive()
|
||||
if activeKey == nil {
|
||||
return errors.New("no active raft TLS key found")
|
||||
}
|
||||
|
||||
// Start the process in a go routine
|
||||
go func() {
|
||||
nextRotationTime := activeKey.CreatedTime.Add(raftTLSRotationPeriod)
|
||||
|
||||
keyCheckInterval := time.NewTicker(1 * time.Minute)
|
||||
defer keyCheckInterval.Stop()
|
||||
|
||||
var backoff bool
|
||||
for {
|
||||
// If we encountered and error we should try to create the key
|
||||
// again.
|
||||
if backoff {
|
||||
nextRotationTime = time.Now().Add(10 * time.Second)
|
||||
backoff = false
|
||||
}
|
||||
|
||||
select {
|
||||
case <-keyCheckInterval.C:
|
||||
err := checkCommitted()
|
||||
if err != nil {
|
||||
logger.Error("failed to activate TLS key", "error", err)
|
||||
}
|
||||
case <-time.After(time.Until(nextRotationTime)):
|
||||
// It's time to rotate the keys
|
||||
next, err := rotateKeyring()
|
||||
if err != nil {
|
||||
logger.Error("failed to rotate TLS key", "error", err)
|
||||
backoff = true
|
||||
continue
|
||||
}
|
||||
|
||||
nextRotationTime = next
|
||||
|
||||
case <-stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) stopPeriodicRaftTLSRotate() {
|
||||
if c.raftTLSRotationStopCh != nil {
|
||||
close(c.raftTLSRotationStopCh)
|
||||
}
|
||||
c.raftTLSRotationStopCh = nil
|
||||
c.raftFollowerStates = nil
|
||||
}
|
||||
|
||||
func (c *Core) checkRaftTLSKeyUpgrades(ctx context.Context) error {
|
||||
raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsKeyringEntry, err := c.barrier.Get(ctx, raftTLSStoragePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tlsKeyringEntry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var keyring raft.RaftTLSKeyring
|
||||
if err := tlsKeyringEntry.DecodeJSON(&keyring); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := raftStorage.SetTLSKeyring(&keyring); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleSnapshotRestore is for the raft backend to hook back into core after a
|
||||
// snapshot is restored so we can clear the necessary caches and handle changing
|
||||
// keyrings or master keys
|
||||
func (c *Core) raftSnapshotRestoreCallback(grabLock bool) func() error {
|
||||
return func() error {
|
||||
c.logger.Info("running post snapshot restore invalidations")
|
||||
|
||||
if grabLock {
|
||||
// Grab statelock
|
||||
if stopped := grabLockOrStop(c.stateLock.Lock, c.stateLock.Unlock, c.standbyStopCh); stopped {
|
||||
c.logger.Error("did not apply snapshot; vault is shutting down")
|
||||
return errors.New("did not apply snapshot; vault is shutting down")
|
||||
}
|
||||
defer c.stateLock.Unlock()
|
||||
}
|
||||
ctx, ctxCancel := context.WithCancel(namespace.RootContext(nil))
|
||||
|
||||
// Purge the cache so we make sure we are operating on fresh data
|
||||
c.physicalCache.Purge(ctx)
|
||||
|
||||
// Reload the keyring in case it changed. If this fails it's likely
|
||||
// we've changed master keys.
|
||||
err := c.performKeyUpgrades(ctx)
|
||||
if err != nil {
|
||||
// The snapshot contained a master key or keyring we couldn't
|
||||
// recover
|
||||
switch c.seal.BarrierType() {
|
||||
case seal.Shamir:
|
||||
// If we are a shamir seal we can't do anything. Just
|
||||
// seal all nodes.
|
||||
|
||||
// Seal ourselves
|
||||
c.logger.Info("failed to perform key upgrades, sealing", "error", err)
|
||||
c.sealInternalWithOptions(false, false)
|
||||
return err
|
||||
default:
|
||||
// If we are using an auto-unseal we can try to use the seal to
|
||||
// unseal again. If the auto-unseal mechanism has changed then
|
||||
// there isn't anything we can do but seal.
|
||||
c.logger.Info("failed to perform key upgrades, reloading using auto seal")
|
||||
keys, err := c.seal.GetStoredKeys(ctx)
|
||||
if err != nil {
|
||||
c.logger.Error("raft snapshot restore failed to get stored keys", "error", err)
|
||||
c.sealInternalWithOptions(false, false)
|
||||
return err
|
||||
}
|
||||
if err := c.barrier.Seal(); err != nil {
|
||||
c.logger.Error("raft snapshot restore failed to seal barrier", "error", err)
|
||||
c.sealInternalWithOptions(false, false)
|
||||
return err
|
||||
}
|
||||
if err := c.barrier.Unseal(ctx, keys[0]); err != nil {
|
||||
c.logger.Error("raft snapshot restore failed to unseal barrier", "error", err)
|
||||
c.sealInternalWithOptions(false, false)
|
||||
return err
|
||||
}
|
||||
c.logger.Info("done reloading master key using auto seal")
|
||||
}
|
||||
}
|
||||
|
||||
if !c.standby {
|
||||
// Go through a preSeal, postUnseal cycle to clear the rest of
|
||||
// vault's in-memory caches
|
||||
c.logger.Info("restarting system after raft snapshot restore")
|
||||
if err := c.preSeal(); err != nil {
|
||||
c.logger.Error("raft snapshot restore failed preSeal", "error", err)
|
||||
return err
|
||||
}
|
||||
{
|
||||
// If the snapshot was taken while another node was leader we
|
||||
// need to reset the leader information to this node.
|
||||
if err := c.underlyingPhysical.Put(ctx, &physical.Entry{
|
||||
Key: CoreLockPath,
|
||||
Value: []byte(c.leaderUUID),
|
||||
}); err != nil {
|
||||
c.logger.Error("cluster setup failed", "error", err)
|
||||
return err
|
||||
}
|
||||
// re-advertise our cluster information
|
||||
if err := c.advertiseLeader(ctx, c.leaderUUID, nil); err != nil {
|
||||
c.logger.Error("cluster setup failed", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.postUnseal(ctx, ctxCancel, standardUnsealStrategy{}); err != nil {
|
||||
c.logger.Error("raft snapshot restore failed postUnseal", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Core) JoinRaftCluster(ctx context.Context, leaderAddr string, tlsConfig *tls.Config, retry bool) (bool, error) {
|
||||
if len(leaderAddr) == 0 {
|
||||
return false, errors.New("No leader address provided")
|
||||
}
|
||||
|
||||
raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return false, errors.New("raft storage not configured")
|
||||
}
|
||||
|
||||
if raftStorage.Initialized() {
|
||||
return false, errors.New("raft is alreay initialized")
|
||||
}
|
||||
|
||||
init, err := c.Initialized(ctx)
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf("failed to check if core is initialized: {{err}}", err)
|
||||
}
|
||||
if init {
|
||||
return false, errwrap.Wrapf("join can't be invoked on an initialized cluster: {{err}}", ErrAlreadyInit)
|
||||
}
|
||||
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
if tlsConfig != nil {
|
||||
transport.TLSClientConfig = tlsConfig.Clone()
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
return false, errwrap.Wrapf("failed to configure TLS: {{err}}", err)
|
||||
}
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
config := api.DefaultConfig()
|
||||
if config.Error != nil {
|
||||
return false, errwrap.Wrapf("failed to create api client: {{err}}", config.Error)
|
||||
}
|
||||
config.Address = leaderAddr
|
||||
config.HttpClient = client
|
||||
config.MaxRetries = 0
|
||||
apiClient, err := api.NewClient(config)
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf("failed to create api client: {{err}}", err)
|
||||
}
|
||||
|
||||
join := func() error {
|
||||
// Unwrap the token
|
||||
secret, err := apiClient.Logical().Write("sys/storage/raft/bootstrap/challenge", map[string]interface{}{
|
||||
"server_id": raftStorage.NodeID(),
|
||||
})
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error during bootstrap init call: {{err}}", err)
|
||||
}
|
||||
if secret == nil {
|
||||
return errors.New("could not retrieve bootstrap package")
|
||||
}
|
||||
|
||||
var sealConfig SealConfig
|
||||
err = mapstructure.Decode(secret.Data["seal_config"], &sealConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sealConfig.Type != c.seal.BarrierType() {
|
||||
return fmt.Errorf("mismatching seal types between leader (%s) and follower (%s)", sealConfig.Type, c.seal.BarrierType())
|
||||
}
|
||||
|
||||
challengeB64, ok := secret.Data["challenge"]
|
||||
if !ok {
|
||||
return errors.New("error during raft bootstrap call, no challenge given")
|
||||
}
|
||||
challengeRaw, err := base64.StdEncoding.DecodeString(challengeB64.(string))
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error decoding challenge: {{err}}", err)
|
||||
}
|
||||
|
||||
eBlob := &physical.EncryptedBlobInfo{}
|
||||
if err := proto.Unmarshal(challengeRaw, eBlob); err != nil {
|
||||
return errwrap.Wrapf("error decoding challenge: {{err}}", err)
|
||||
}
|
||||
|
||||
if c.seal.BarrierType() == seal.Shamir {
|
||||
c.raftUnseal = true
|
||||
c.raftChallenge = eBlob
|
||||
c.raftLeaderClient = apiClient
|
||||
c.raftLeaderBarrierConfig = &sealConfig
|
||||
c.seal.SetBarrierConfig(ctx, &sealConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.joinRaftSendAnswer(ctx, apiClient, eBlob, c.seal.GetAccess()); err != nil {
|
||||
return errwrap.Wrapf("failed to send answer to leader node: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
switch retry {
|
||||
case true:
|
||||
go func() {
|
||||
for {
|
||||
// TODO add a way to shut this down
|
||||
err := join()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
c.logger.Error("failed to join raft cluster", "error", err)
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}()
|
||||
|
||||
// Backgrounded so return false
|
||||
return false, nil
|
||||
default:
|
||||
if err := join(); err != nil {
|
||||
c.logger.Error("failed to join raft cluster", "error", err)
|
||||
return false, errwrap.Wrapf("failed to join raft cluster: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// This is used in tests to override the cluster address
|
||||
var UpdateClusterAddrForTests bool
|
||||
|
||||
func (c *Core) joinRaftSendAnswer(ctx context.Context, leaderClient *api.Client, challenge *physical.EncryptedBlobInfo, sealAccess seal.Access) error {
|
||||
if challenge == nil {
|
||||
return errors.New("raft challenge is nil")
|
||||
}
|
||||
|
||||
raftStorage, ok := c.underlyingPhysical.(*raft.RaftBackend)
|
||||
if !ok {
|
||||
return errors.New("raft storage not in use")
|
||||
}
|
||||
|
||||
if raftStorage.Initialized() {
|
||||
return errors.New("raft is already initialized")
|
||||
}
|
||||
|
||||
plaintext, err := sealAccess.Decrypt(ctx, challenge)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error decrypting challenge: {{err}}", err)
|
||||
}
|
||||
|
||||
parsedClusterAddr, err := url.Parse(c.clusterAddr)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error parsing cluster address: {{err}}", err)
|
||||
}
|
||||
clusterAddr := parsedClusterAddr.Host
|
||||
if UpdateClusterAddrForTests && strings.HasSuffix(clusterAddr, ":0") {
|
||||
// We are testing and have an address provider, so just create a random
|
||||
// addr, it will be overwritten later.
|
||||
var err error
|
||||
clusterAddr, err = uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
answerReq := leaderClient.NewRequest("PUT", "/v1/sys/storage/raft/bootstrap/answer")
|
||||
if err := answerReq.SetJSONBody(map[string]interface{}{
|
||||
"answer": base64.StdEncoding.EncodeToString(plaintext),
|
||||
"cluster_addr": clusterAddr,
|
||||
"server_id": raftStorage.NodeID(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
answerRespJson, err := leaderClient.RawRequestWithContext(ctx, answerReq)
|
||||
if answerRespJson != nil {
|
||||
defer answerRespJson.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var answerResp answerRespData
|
||||
if err := jsonutil.DecodeJSONFromReader(answerRespJson.Body, &answerResp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raftStorage.Bootstrap(ctx, answerResp.Data.Peers)
|
||||
|
||||
err = c.startClusterListener(ctx)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("error starting cluster: {{err}}", err)
|
||||
}
|
||||
|
||||
raftStorage.SetRestoreCallback(c.raftSnapshotRestoreCallback(true))
|
||||
err = raftStorage.SetupCluster(ctx, answerResp.Data.TLSKeyring, c.clusterListener)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("failed to setup raft cluster: {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) isRaftUnseal() bool {
|
||||
return c.raftUnseal
|
||||
}
|
||||
|
||||
type answerRespData struct {
|
||||
Data answerResp `json:"data"`
|
||||
}
|
||||
|
||||
type answerResp struct {
|
||||
Peers []raft.Peer `json:"peers"`
|
||||
TLSKeyring *raft.RaftTLSKeyring `json:"tls_keyring"`
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -17,6 +18,8 @@ import (
|
|||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/shamir"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -528,6 +531,14 @@ func (c *Core) performBarrierRekey(ctx context.Context, newMasterKey []byte) log
|
|||
}
|
||||
|
||||
c.barrierRekeyConfig.RekeyProgress = nil
|
||||
if c.seal.BarrierType() == seal.Shamir {
|
||||
_, err := c.seal.GetAccess().(*shamirseal.ShamirSeal).SetConfig(map[string]string{
|
||||
"key": base64.StdEncoding.EncodeToString(newMasterKey),
|
||||
})
|
||||
if err != nil {
|
||||
return logical.CodedError(http.StatusInternalServerError, errwrap.Wrapf("failed to update seal access: {{err}}", err).Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/helper/forwarding"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/vault/cluster"
|
||||
"github.com/hashicorp/vault/vault/replication"
|
||||
|
|
@ -118,14 +119,14 @@ func (rf *requestForwardingHandler) ServerLookup(ctx context.Context, clientHell
|
|||
}
|
||||
|
||||
// CALookup satisfies the ClusterHandler interface and returns the ha ca cert.
|
||||
func (rf *requestForwardingHandler) CALookup(ctx context.Context) (*x509.Certificate, error) {
|
||||
func (rf *requestForwardingHandler) CALookup(ctx context.Context) ([]*x509.Certificate, error) {
|
||||
parsedCert := rf.core.localClusterParsedCert.Load().(*x509.Certificate)
|
||||
|
||||
if parsedCert == nil {
|
||||
return nil, fmt.Errorf("forwarding connection client but no local cert")
|
||||
}
|
||||
|
||||
return parsedCert, nil
|
||||
return []*x509.Certificate{parsedCert}, nil
|
||||
}
|
||||
|
||||
// Handoff serves a request forwarding connection.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/forwarding"
|
||||
"github.com/hashicorp/vault/physical/raft"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/vault/replication"
|
||||
cache "github.com/patrickmn/go-cache"
|
||||
|
|
@ -72,10 +73,22 @@ func (s *forwardedRequestRPCServer) Echo(ctx context.Context, in *EchoRequest) (
|
|||
if in.ClusterAddr != "" {
|
||||
s.core.clusterPeerClusterAddrsCache.Set(in.ClusterAddr, nil, 0)
|
||||
}
|
||||
return &EchoReply{
|
||||
|
||||
if in.RaftAppliedIndex > 0 && len(in.RaftNodeID) > 0 && s.core.raftFollowerStates != nil {
|
||||
s.core.raftFollowerStates.update(in.RaftNodeID, in.RaftAppliedIndex)
|
||||
}
|
||||
|
||||
reply := &EchoReply{
|
||||
Message: "pong",
|
||||
ReplicationState: uint32(s.core.ReplicationState()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if raftStorage, ok := s.core.underlyingPhysical.(*raft.RaftBackend); ok {
|
||||
reply.RaftAppliedIndex = raftStorage.AppliedIndex()
|
||||
reply.RaftNodeID = raftStorage.NodeID()
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
type forwardingClient struct {
|
||||
|
|
@ -96,11 +109,18 @@ func (c *forwardingClient) startHeartbeat() {
|
|||
clusterAddr := c.core.clusterAddr
|
||||
c.core.stateLock.RUnlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.echoContext, 2*time.Second)
|
||||
resp, err := c.RequestForwardingClient.Echo(ctx, &EchoRequest{
|
||||
req := &EchoRequest{
|
||||
Message: "ping",
|
||||
ClusterAddr: clusterAddr,
|
||||
})
|
||||
}
|
||||
|
||||
if raftStorage, ok := c.core.underlyingPhysical.(*raft.RaftBackend); ok {
|
||||
req.RaftAppliedIndex = raftStorage.AppliedIndex()
|
||||
req.RaftNodeID = raftStorage.NodeID()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.echoContext, 2*time.Second)
|
||||
resp, err := c.RequestForwardingClient.Echo(ctx, req)
|
||||
cancel()
|
||||
if err != nil {
|
||||
c.core.logger.Debug("forwarding: error sending echo request to active node", "error", err)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import (
|
|||
proto "github.com/golang/protobuf/proto"
|
||||
forwarding "github.com/hashicorp/vault/helper/forwarding"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
math "math"
|
||||
)
|
||||
|
||||
|
|
@ -33,6 +31,8 @@ type EchoRequest struct {
|
|||
// ClusterAddrs is used to send up a list of cluster addresses to a dr
|
||||
// primary from a dr secondary
|
||||
ClusterAddrs []string `protobuf:"bytes,3,rep,name=cluster_addrs,json=clusterAddrs,proto3" json:"cluster_addrs,omitempty"`
|
||||
RaftAppliedIndex uint64 `protobuf:"varint,4,opt,name=raft_applied_index,json=raftAppliedIndex,proto3" json:"raft_applied_index,omitempty"`
|
||||
RaftNodeID string `protobuf:"bytes,5,opt,name=raft_node_id,json=raftNodeId,proto3" json:"raft_node_id,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
|
@ -84,10 +84,26 @@ func (m *EchoRequest) GetClusterAddrs() []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *EchoRequest) GetRaftAppliedIndex() uint64 {
|
||||
if m != nil {
|
||||
return m.RaftAppliedIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *EchoRequest) GetRaftNodeID() string {
|
||||
if m != nil {
|
||||
return m.RaftNodeID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type EchoReply struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
ClusterAddrs []string `protobuf:"bytes,2,rep,name=cluster_addrs,json=clusterAddrs,proto3" json:"cluster_addrs,omitempty"`
|
||||
ReplicationState uint32 `protobuf:"varint,3,opt,name=replication_state,json=replicationState,proto3" json:"replication_state,omitempty"`
|
||||
RaftAppliedIndex uint64 `protobuf:"varint,4,opt,name=raft_applied_index,json=raftAppliedIndex,proto3" json:"raft_applied_index,omitempty"`
|
||||
RaftNodeID string `protobuf:"bytes,5,opt,name=raft_node_id,json=raftNodeId,proto3" json:"raft_node_id,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
|
@ -139,6 +155,20 @@ func (m *EchoReply) GetReplicationState() uint32 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (m *EchoReply) GetRaftAppliedIndex() uint64 {
|
||||
if m != nil {
|
||||
return m.RaftAppliedIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *EchoReply) GetRaftNodeID() string {
|
||||
if m != nil {
|
||||
return m.RaftNodeID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ClientKey struct {
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
X []byte `protobuf:"bytes,2,opt,name=x,proto3" json:"x,omitempty"`
|
||||
|
|
@ -234,8 +264,8 @@ func (m *PerfStandbyElectionInput) XXX_DiscardUnknown() {
|
|||
var xxx_messageInfo_PerfStandbyElectionInput proto.InternalMessageInfo
|
||||
|
||||
type PerfStandbyElectionResponse struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ClusterId string `protobuf:"bytes,2,opt,name=cluster_id,json=clusterId,proto3" json:"cluster_id,omitempty"`
|
||||
ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ClusterID string `protobuf:"bytes,2,opt,name=cluster_id,json=clusterId,proto3" json:"cluster_id,omitempty"`
|
||||
PrimaryClusterAddr string `protobuf:"bytes,3,opt,name=primary_cluster_addr,json=primaryClusterAddr,proto3" json:"primary_cluster_addr,omitempty"`
|
||||
CaCert []byte `protobuf:"bytes,4,opt,name=ca_cert,json=caCert,proto3" json:"ca_cert,omitempty"`
|
||||
ClientCert []byte `protobuf:"bytes,5,opt,name=client_cert,json=clientCert,proto3" json:"client_cert,omitempty"`
|
||||
|
|
@ -270,16 +300,16 @@ func (m *PerfStandbyElectionResponse) XXX_DiscardUnknown() {
|
|||
|
||||
var xxx_messageInfo_PerfStandbyElectionResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *PerfStandbyElectionResponse) GetId() string {
|
||||
func (m *PerfStandbyElectionResponse) GetID() string {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PerfStandbyElectionResponse) GetClusterId() string {
|
||||
func (m *PerfStandbyElectionResponse) GetClusterID() string {
|
||||
if m != nil {
|
||||
return m.ClusterId
|
||||
return m.ClusterID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -325,38 +355,42 @@ func init() {
|
|||
}
|
||||
|
||||
var fileDescriptor_f5f7512e4ab7b58a = []byte{
|
||||
// 493 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x41, 0x6f, 0x1a, 0x3d,
|
||||
0x10, 0x8d, 0x81, 0x10, 0x31, 0x90, 0x88, 0xf8, 0x8b, 0xf4, 0xad, 0xa8, 0xa2, 0x90, 0xad, 0x54,
|
||||
0x21, 0x55, 0xda, 0x8d, 0xd2, 0x73, 0x0f, 0x2d, 0x4a, 0x25, 0xd4, 0x4b, 0xb5, 0xb9, 0xf5, 0xb2,
|
||||
0x32, 0xf6, 0x04, 0xac, 0x2e, 0x6b, 0xd7, 0x36, 0x49, 0xf6, 0x27, 0xf7, 0xd6, 0x9f, 0x50, 0xad,
|
||||
0xd7, 0x04, 0x10, 0x4d, 0x2f, 0x68, 0xe7, 0xcd, 0x63, 0xde, 0xf8, 0xf9, 0x19, 0xde, 0x3d, 0xb2,
|
||||
0x75, 0xe1, 0x52, 0x83, 0x3f, 0xd7, 0x68, 0x5d, 0xfe, 0xa0, 0xcc, 0x13, 0x33, 0x42, 0x96, 0x8b,
|
||||
0xdc, 0xa2, 0x79, 0x94, 0x1c, 0x13, 0x6d, 0x94, 0x53, 0xf4, 0xd8, 0xf3, 0x46, 0x97, 0x4b, 0x2c,
|
||||
0x34, 0x9a, 0x74, 0xcb, 0x4b, 0x5d, 0xa5, 0xd1, 0x36, 0xac, 0x58, 0x41, 0xff, 0x8e, 0x2f, 0x55,
|
||||
0xd6, 0x4c, 0xa3, 0x11, 0x9c, 0xac, 0xd0, 0x5a, 0xb6, 0xc0, 0x88, 0x8c, 0xc9, 0xa4, 0x97, 0x6d,
|
||||
0x4a, 0x7a, 0x0d, 0x03, 0x5e, 0xac, 0xad, 0x43, 0x93, 0x33, 0x21, 0x4c, 0xd4, 0xf2, 0xed, 0x7e,
|
||||
0xc0, 0x3e, 0x09, 0x61, 0xe8, 0x5b, 0x38, 0xdd, 0xa5, 0xd8, 0xa8, 0x3d, 0x6e, 0x4f, 0x7a, 0xd9,
|
||||
0x60, 0x87, 0x63, 0xe3, 0x27, 0xe8, 0x35, 0x82, 0xba, 0xa8, 0xfe, 0x21, 0x77, 0x30, 0xab, 0x75,
|
||||
0x38, 0x8b, 0xbe, 0x87, 0x73, 0x83, 0xba, 0x90, 0x9c, 0x39, 0xa9, 0xca, 0xdc, 0x3a, 0xe6, 0x30,
|
||||
0x6a, 0x8f, 0xc9, 0xe4, 0x34, 0x1b, 0xee, 0x34, 0xee, 0x6b, 0x3c, 0x9e, 0x41, 0x6f, 0x5a, 0x48,
|
||||
0x2c, 0xdd, 0x57, 0xac, 0x28, 0x85, 0x4e, 0xed, 0x42, 0x50, 0xf5, 0xdf, 0x74, 0x00, 0xe4, 0xd9,
|
||||
0x1f, 0x6b, 0x90, 0x91, 0xe7, 0xba, 0xaa, 0xfc, 0xac, 0x41, 0x46, 0xaa, 0xba, 0x12, 0x51, 0xa7,
|
||||
0xa9, 0x44, 0x3c, 0x82, 0xe8, 0x1b, 0x9a, 0x87, 0x7b, 0xc7, 0x4a, 0x31, 0xaf, 0xee, 0x0a, 0xe4,
|
||||
0xb5, 0xcc, 0xac, 0xd4, 0x6b, 0x17, 0xff, 0x22, 0xf0, 0xe6, 0x2f, 0xcd, 0x0c, 0xad, 0x56, 0xa5,
|
||||
0x45, 0x7a, 0x06, 0x2d, 0x29, 0x82, 0x6e, 0x4b, 0x0a, 0x7a, 0x09, 0xb0, 0x39, 0xa8, 0x14, 0xc1,
|
||||
0xd5, 0x5e, 0x40, 0x66, 0x82, 0xde, 0xc0, 0x85, 0x36, 0x72, 0xc5, 0x4c, 0x95, 0xef, 0xd9, 0xdf,
|
||||
0xf6, 0x44, 0x1a, 0x7a, 0xd3, 0x9d, 0x5b, 0xf8, 0x1f, 0x4e, 0x38, 0xcb, 0x39, 0x1a, 0x17, 0x16,
|
||||
0xee, 0x72, 0x36, 0x45, 0xe3, 0xe8, 0x15, 0xf4, 0xb9, 0x37, 0xa0, 0x69, 0x1e, 0xfb, 0x26, 0x34,
|
||||
0x90, 0x27, 0xa4, 0x10, 0xaa, 0xfc, 0x07, 0x56, 0x51, 0x77, 0x4c, 0x26, 0xfd, 0xdb, 0x61, 0xe2,
|
||||
0x63, 0x94, 0xbc, 0x58, 0x57, 0x2f, 0x17, 0x3e, 0x6f, 0x7f, 0x13, 0x38, 0x0f, 0xc9, 0xf9, 0xf2,
|
||||
0x12, 0x2f, 0xfa, 0x11, 0xce, 0x42, 0xb5, 0x49, 0xd5, 0x7f, 0xc9, 0x36, 0x7d, 0x49, 0x00, 0x47,
|
||||
0x17, 0xfb, 0x60, 0x63, 0x4f, 0x7c, 0x44, 0x13, 0xe8, 0xd4, 0x01, 0xa1, 0x34, 0x28, 0xef, 0xc4,
|
||||
0x73, 0x34, 0xdc, 0xc3, 0x74, 0x51, 0xc5, 0x47, 0xb4, 0x80, 0xeb, 0xda, 0x6f, 0x65, 0x56, 0xac,
|
||||
0xe4, 0x78, 0x60, 0x7b, 0xb3, 0xc1, 0x55, 0xf8, 0xe3, 0x6b, 0xd7, 0x36, 0x8a, 0x5f, 0x27, 0x6c,
|
||||
0x77, 0xbb, 0x21, 0x9f, 0xe3, 0xef, 0xe3, 0x85, 0x74, 0xcb, 0xf5, 0x3c, 0xe1, 0x6a, 0x95, 0x2e,
|
||||
0x99, 0x5d, 0x4a, 0xae, 0x8c, 0x4e, 0x9b, 0x47, 0xe9, 0x7f, 0xe7, 0x5d, 0xff, 0xb4, 0x3e, 0xfc,
|
||||
0x09, 0x00, 0x00, 0xff, 0xff, 0x03, 0x94, 0x0a, 0x17, 0xaa, 0x03, 0x00, 0x00,
|
||||
// 552 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x51, 0x6b, 0xdb, 0x3c,
|
||||
0x14, 0xad, 0x92, 0xb4, 0x25, 0x37, 0x6e, 0x49, 0xf5, 0x15, 0x3e, 0x93, 0x51, 0xea, 0x7a, 0x30,
|
||||
0x02, 0x1b, 0x76, 0xe9, 0x9e, 0xf7, 0xd0, 0x95, 0x0e, 0xc2, 0x60, 0x0c, 0xf7, 0x6d, 0x2f, 0x46,
|
||||
0x95, 0x6e, 0x13, 0x31, 0xc7, 0xd6, 0x24, 0xa5, 0x8b, 0x7f, 0xdd, 0x1e, 0xf7, 0x5b, 0xf6, 0xb6,
|
||||
0x9f, 0x30, 0x2c, 0x2b, 0x4d, 0x42, 0xdb, 0x3d, 0xed, 0x25, 0xe8, 0x9e, 0x73, 0x92, 0x7b, 0x74,
|
||||
0x74, 0x6f, 0xe0, 0xd5, 0x3d, 0x5b, 0x14, 0x36, 0xd5, 0xf8, 0x6d, 0x81, 0xc6, 0xe6, 0x77, 0x95,
|
||||
0xfe, 0xce, 0xb4, 0x90, 0xe5, 0x34, 0x37, 0xa8, 0xef, 0x25, 0xc7, 0x44, 0xe9, 0xca, 0x56, 0x74,
|
||||
0xd7, 0xe9, 0x46, 0x27, 0x33, 0x2c, 0x14, 0xea, 0x74, 0xad, 0x4b, 0x6d, 0xad, 0xd0, 0xb4, 0xaa,
|
||||
0xf8, 0x07, 0x81, 0xc1, 0x35, 0x9f, 0x55, 0x59, 0xfb, 0x73, 0x34, 0x84, 0xfd, 0x39, 0x1a, 0xc3,
|
||||
0xa6, 0x18, 0x92, 0x88, 0x8c, 0xfb, 0xd9, 0xaa, 0xa4, 0x67, 0x10, 0xf0, 0x62, 0x61, 0x2c, 0xea,
|
||||
0x9c, 0x09, 0xa1, 0xc3, 0x8e, 0xa3, 0x07, 0x1e, 0xbb, 0x14, 0x42, 0xd3, 0x97, 0x70, 0xb0, 0x29,
|
||||
0x31, 0x61, 0x37, 0xea, 0x8e, 0xfb, 0x59, 0xb0, 0xa1, 0x31, 0xf4, 0x0d, 0x50, 0xcd, 0xee, 0x6c,
|
||||
0xce, 0x94, 0x2a, 0x24, 0x8a, 0x5c, 0x96, 0x02, 0x97, 0x61, 0x2f, 0x22, 0xe3, 0x5e, 0x36, 0x6c,
|
||||
0x98, 0xcb, 0x96, 0x98, 0x34, 0x38, 0x8d, 0x20, 0x70, 0xea, 0xb2, 0x12, 0x98, 0x4b, 0x11, 0xee,
|
||||
0xba, 0xae, 0xd0, 0x60, 0x9f, 0x2a, 0x81, 0x13, 0x11, 0xff, 0x24, 0xd0, 0x6f, 0x6f, 0xa0, 0x8a,
|
||||
0xfa, 0x2f, 0xfe, 0x1f, 0x99, 0xeb, 0x3c, 0x61, 0xee, 0x35, 0x1c, 0x69, 0x54, 0x85, 0xe4, 0xcc,
|
||||
0xca, 0xaa, 0xcc, 0x8d, 0x65, 0x16, 0xc3, 0x6e, 0x44, 0xc6, 0x07, 0xd9, 0x70, 0x83, 0xb8, 0x69,
|
||||
0xf0, 0x7f, 0x7e, 0x93, 0x09, 0xf4, 0xaf, 0x0a, 0x89, 0xa5, 0xfd, 0x88, 0x35, 0xa5, 0xd0, 0x6b,
|
||||
0xde, 0xc9, 0xdf, 0xc2, 0x9d, 0x69, 0x00, 0x64, 0xe9, 0x72, 0x0f, 0x32, 0xb2, 0x6c, 0xaa, 0xda,
|
||||
0x79, 0x0b, 0x32, 0x52, 0x37, 0x95, 0x70, 0xbd, 0x83, 0x8c, 0x88, 0x78, 0x04, 0xe1, 0x67, 0xd4,
|
||||
0x77, 0x37, 0x96, 0x95, 0xe2, 0xb6, 0xbe, 0x2e, 0x90, 0x37, 0xb6, 0x27, 0xa5, 0x5a, 0xd8, 0xf8,
|
||||
0x17, 0x81, 0x17, 0x4f, 0x90, 0x19, 0x1a, 0x55, 0x95, 0x06, 0xe9, 0x21, 0x74, 0xa4, 0xf0, 0x7d,
|
||||
0x3b, 0x52, 0xd0, 0x13, 0x80, 0x55, 0x70, 0x52, 0xf8, 0x67, 0xef, 0x7b, 0x64, 0x22, 0xe8, 0x39,
|
||||
0x1c, 0x2b, 0x2d, 0xe7, 0x4c, 0xd7, 0xf9, 0xd6, 0x7c, 0x74, 0x9d, 0x90, 0x7a, 0xee, 0x6a, 0x63,
|
||||
0x4c, 0xfe, 0x87, 0x7d, 0xce, 0x72, 0x8e, 0xda, 0x7a, 0xc3, 0x7b, 0x9c, 0x5d, 0xa1, 0xb6, 0xf4,
|
||||
0x14, 0x06, 0xdc, 0x05, 0xd0, 0x92, 0xbb, 0x8e, 0x84, 0x16, 0x72, 0x82, 0x14, 0x7c, 0x95, 0x7f,
|
||||
0xc5, 0x3a, 0xdc, 0x8b, 0xc8, 0x78, 0x70, 0x31, 0x4c, 0xdc, 0xa0, 0x27, 0x0f, 0xd1, 0x35, 0xe6,
|
||||
0xfc, 0xf1, 0xe2, 0x37, 0x81, 0x23, 0x3f, 0xda, 0x1f, 0x1e, 0x16, 0x80, 0xbe, 0x83, 0x43, 0x5f,
|
||||
0xad, 0xc6, 0xfe, 0xbf, 0x64, 0xbd, 0x1f, 0x89, 0x07, 0x47, 0xc7, 0xdb, 0x60, 0x1b, 0x4f, 0xbc,
|
||||
0x43, 0x13, 0xe8, 0x35, 0x03, 0x47, 0xa9, 0xef, 0xbc, 0xb1, 0x3f, 0xa3, 0xe1, 0x16, 0xa6, 0x8a,
|
||||
0x3a, 0xde, 0xa1, 0x05, 0x9c, 0x35, 0x79, 0x57, 0x7a, 0xce, 0x4a, 0x8e, 0x8f, 0x62, 0x6f, 0x1d,
|
||||
0x9c, 0xfa, 0x2f, 0x3e, 0xf7, 0x6c, 0xa3, 0xf8, 0x79, 0xc1, 0xda, 0xdb, 0x39, 0x79, 0x1f, 0x7f,
|
||||
0x89, 0xa6, 0xd2, 0xce, 0x16, 0xb7, 0x09, 0xaf, 0xe6, 0xe9, 0x8c, 0x99, 0x99, 0xe4, 0x95, 0x56,
|
||||
0x69, 0xfb, 0xb7, 0xe1, 0x3e, 0x6f, 0xf7, 0xdc, 0xf2, 0xbf, 0xfd, 0x13, 0x00, 0x00, 0xff, 0xff,
|
||||
0x67, 0xc6, 0xa7, 0xe1, 0x4c, 0x04, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
|
@ -441,20 +475,6 @@ type RequestForwardingServer interface {
|
|||
PerformanceStandbyElectionRequest(*PerfStandbyElectionInput, RequestForwarding_PerformanceStandbyElectionRequestServer) error
|
||||
}
|
||||
|
||||
// UnimplementedRequestForwardingServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedRequestForwardingServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedRequestForwardingServer) ForwardRequest(ctx context.Context, req *forwarding.Request) (*forwarding.Response, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ForwardRequest not implemented")
|
||||
}
|
||||
func (*UnimplementedRequestForwardingServer) Echo(ctx context.Context, req *EchoRequest) (*EchoReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Echo not implemented")
|
||||
}
|
||||
func (*UnimplementedRequestForwardingServer) PerformanceStandbyElectionRequest(req *PerfStandbyElectionInput, srv RequestForwarding_PerformanceStandbyElectionRequestServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method PerformanceStandbyElectionRequest not implemented")
|
||||
}
|
||||
|
||||
func RegisterRequestForwardingServer(s *grpc.Server, srv RequestForwardingServer) {
|
||||
s.RegisterService(&_RequestForwarding_serviceDesc, srv)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,17 @@ message EchoRequest {
|
|||
// ClusterAddrs is used to send up a list of cluster addresses to a dr
|
||||
// primary from a dr secondary
|
||||
repeated string cluster_addrs = 3;
|
||||
|
||||
uint64 raft_applied_index = 4;
|
||||
string raft_node_id = 5;
|
||||
}
|
||||
|
||||
message EchoReply {
|
||||
string message = 1;
|
||||
repeated string cluster_addrs = 2;
|
||||
uint32 replication_state = 3;
|
||||
uint64 raft_applied_index = 4;
|
||||
string raft_node_id = 5;
|
||||
}
|
||||
|
||||
message ClientKey {
|
||||
|
|
|
|||
|
|
@ -72,9 +72,12 @@ type Seal interface {
|
|||
SetCachedRecoveryConfig(*SealConfig)
|
||||
SetRecoveryKey(context.Context, []byte) error
|
||||
VerifyRecoveryKey(context.Context, []byte) error
|
||||
|
||||
GetAccess() seal.Access
|
||||
}
|
||||
|
||||
type defaultSeal struct {
|
||||
access seal.Access
|
||||
config atomic.Value
|
||||
core *Core
|
||||
PretendToAllowStoredShares bool
|
||||
|
|
@ -82,8 +85,10 @@ type defaultSeal struct {
|
|||
PretendRecoveryKey []byte
|
||||
}
|
||||
|
||||
func NewDefaultSeal() Seal {
|
||||
ret := &defaultSeal{}
|
||||
func NewDefaultSeal(lowLevel seal.Access) Seal {
|
||||
ret := &defaultSeal{
|
||||
access: lowLevel,
|
||||
}
|
||||
ret.config.Store((*SealConfig)(nil))
|
||||
return ret
|
||||
}
|
||||
|
|
@ -95,6 +100,14 @@ func (d *defaultSeal) checkCore() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *defaultSeal) GetAccess() seal.Access {
|
||||
return d.access
|
||||
}
|
||||
|
||||
func (d *defaultSeal) SetAccess(access seal.Access) {
|
||||
d.access = access
|
||||
}
|
||||
|
||||
func (d *defaultSeal) SetCore(core *Core) {
|
||||
d.core = core
|
||||
}
|
||||
|
|
@ -191,6 +204,13 @@ func (d *defaultSeal) SetBarrierConfig(ctx context.Context, config *SealConfig)
|
|||
|
||||
config.Type = d.BarrierType()
|
||||
|
||||
// If we are doing a raft unseal we do not want to persit the barrier config
|
||||
// because storage isn't setup yet.
|
||||
if d.core.isRaftUnseal() {
|
||||
d.config.Store(config.Clone())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode the seal configuration
|
||||
buf, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
|
|
@ -273,33 +293,33 @@ func (d *defaultSeal) SetRecoveryKey(ctx context.Context, key []byte) error {
|
|||
// SealConfig is used to describe the seal configuration
|
||||
type SealConfig struct {
|
||||
// The type, for sanity checking
|
||||
Type string `json:"type"`
|
||||
Type string `json:"type" mapstructure:"type"`
|
||||
|
||||
// SecretShares is the number of shares the secret is split into. This is
|
||||
// the N value of Shamir.
|
||||
SecretShares int `json:"secret_shares"`
|
||||
SecretShares int `json:"secret_shares" mapstructure:"secret_shares"`
|
||||
|
||||
// SecretThreshold is the number of parts required to open the vault. This
|
||||
// is the T value of Shamir.
|
||||
SecretThreshold int `json:"secret_threshold"`
|
||||
SecretThreshold int `json:"secret_threshold" mapstructure:"secret_threshold"`
|
||||
|
||||
// PGPKeys is the array of public PGP keys used, if requested, to encrypt
|
||||
// the output unseal tokens. If provided, it sets the value of
|
||||
// SecretShares. Ordering is important.
|
||||
PGPKeys []string `json:"pgp_keys"`
|
||||
PGPKeys []string `json:"pgp_keys" mapstructure:"pgp_keys"`
|
||||
|
||||
// Nonce is a nonce generated by Vault used to ensure that when unseal keys
|
||||
// are submitted for a rekey operation, the rekey operation itself is the
|
||||
// one intended. This prevents hijacking of the rekey operation, since it
|
||||
// is unauthenticated.
|
||||
Nonce string `json:"nonce"`
|
||||
Nonce string `json:"nonce" mapstructure:"nonce"`
|
||||
|
||||
// Backup indicates whether or not a backup of PGP-encrypted unseal keys
|
||||
// should be stored at coreUnsealKeysBackupPath after successful rekeying.
|
||||
Backup bool `json:"backup"`
|
||||
Backup bool `json:"backup" mapstructure:"backup"`
|
||||
|
||||
// How many keys to store, for seals that support storage.
|
||||
StoredShares int `json:"stored_shares"`
|
||||
StoredShares int `json:"stored_shares" mapstructure:"stored_shares"`
|
||||
|
||||
// Stores the progress of the rekey operation (key shares)
|
||||
RekeyProgress [][]byte `json:"-"`
|
||||
|
|
|
|||
125
vault/seal/shamir/shamir.go
Normal file
125
vault/seal/shamir/shamir.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package shamir
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/sdk/physical"
|
||||
"github.com/hashicorp/vault/vault/seal"
|
||||
)
|
||||
|
||||
// ShamirSeal implements the seal.Access interface for Shamir unseal
|
||||
type ShamirSeal struct {
|
||||
logger log.Logger
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
// Ensure that we are implementing AutoSealAccess
|
||||
var _ seal.Access = (*ShamirSeal)(nil)
|
||||
|
||||
// NewSeal creates a new ShamirSeal with the provided logger
|
||||
func NewSeal(logger log.Logger) *ShamirSeal {
|
||||
seal := &ShamirSeal{
|
||||
logger: logger,
|
||||
}
|
||||
return seal
|
||||
}
|
||||
|
||||
// SetConfig sets the fields on the ShamirSeal object based on
|
||||
// values from the config parameter.
|
||||
func (s *ShamirSeal) SetConfig(config map[string]string) (map[string]string, error) {
|
||||
// Map that holds non-sensitive configuration info
|
||||
sealInfo := make(map[string]string)
|
||||
|
||||
if config == nil || config["key"] == "" {
|
||||
return sealInfo, nil
|
||||
}
|
||||
|
||||
keyB64 := config["key"]
|
||||
key, err := base64.StdEncoding.DecodeString(keyB64)
|
||||
if err != nil {
|
||||
return sealInfo, err
|
||||
}
|
||||
|
||||
aesCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return sealInfo, err
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(aesCipher)
|
||||
if err != nil {
|
||||
return sealInfo, err
|
||||
}
|
||||
|
||||
s.aead = aead
|
||||
|
||||
return sealInfo, nil
|
||||
}
|
||||
|
||||
// Init is called during core.Initialize. No-op at the moment.
|
||||
func (s *ShamirSeal) Init(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize is called during shutdown. This is a no-op since
|
||||
// ShamirSeal doesn't require any cleanup.
|
||||
func (s *ShamirSeal) Finalize(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SealType returns the seal type for this particular seal implementation.
|
||||
func (s *ShamirSeal) SealType() string {
|
||||
return seal.Shamir
|
||||
}
|
||||
|
||||
// KeyID returns the last known key id.
|
||||
func (s *ShamirSeal) KeyID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Encrypt is used to encrypt the plaintext using the aead held by the seal.
|
||||
func (s *ShamirSeal) Encrypt(_ context.Context, plaintext []byte) (*physical.EncryptedBlobInfo, error) {
|
||||
if plaintext == nil {
|
||||
return nil, fmt.Errorf("given plaintext for encryption is nil")
|
||||
}
|
||||
|
||||
if s.aead == nil {
|
||||
return nil, errors.New("aead is not configured in the seal")
|
||||
}
|
||||
|
||||
iv, err := uuid.GenerateRandomBytes(12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := s.aead.Seal(nil, iv, plaintext, nil)
|
||||
|
||||
return &physical.EncryptedBlobInfo{
|
||||
Ciphertext: append(iv, ciphertext...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ShamirSeal) Decrypt(_ context.Context, in *physical.EncryptedBlobInfo) ([]byte, error) {
|
||||
if in == nil {
|
||||
return nil, fmt.Errorf("given plaintext for encryption is nil")
|
||||
}
|
||||
|
||||
if s.aead == nil {
|
||||
return nil, errors.New("aead is not configured in the seal")
|
||||
}
|
||||
|
||||
iv, ciphertext := in.Ciphertext[:12], in.Ciphertext[12:]
|
||||
|
||||
plaintext, err := s.aead.Open(nil, iv, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
|
@ -40,6 +40,10 @@ func NewAutoSeal(lowLevel seal.Access) Seal {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (d *autoSeal) GetAccess() seal.Access {
|
||||
return d.Access
|
||||
}
|
||||
|
||||
func (d *autoSeal) checkCore() error {
|
||||
if d.core == nil {
|
||||
return fmt.Errorf("seal does not have a core set")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func TestDefaultSeal_Config(t *testing.T) {
|
|||
|
||||
core, _, _ := TestCoreUnsealed(t)
|
||||
|
||||
defSeal := NewDefaultSeal()
|
||||
defSeal := NewDefaultSeal(nil)
|
||||
defSeal.SetCore(core)
|
||||
err := defSeal.SetBarrierConfig(context.Background(), bc)
|
||||
if err != nil {
|
||||
|
|
@ -31,7 +31,7 @@ func TestDefaultSeal_Config(t *testing.T) {
|
|||
}
|
||||
|
||||
// Now, test without the benefit of the cached value in the seal
|
||||
defSeal = NewDefaultSeal()
|
||||
defSeal = NewDefaultSeal(nil)
|
||||
defSeal.SetCore(core)
|
||||
newBc, err = defSeal.BarrierConfig(context.Background())
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package vault
|
|||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
testing "github.com/mitchellh/go-testing-interface"
|
||||
)
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ type TestSealOpts struct {
|
|||
StoredKeysDisabled bool
|
||||
RecoveryKeysDisabled bool
|
||||
Secret []byte
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
func testCoreUnsealedWithConfigs(t testing.T, barrierConf, recoveryConf *SealConfig) (*Core, [][]byte, [][]byte, string) {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
package vault
|
||||
|
||||
import testing "github.com/mitchellh/go-testing-interface"
|
||||
import (
|
||||
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
|
||||
testing "github.com/mitchellh/go-testing-interface"
|
||||
)
|
||||
|
||||
func NewTestSeal(testing.T, *TestSealOpts) Seal {
|
||||
return NewDefaultSeal()
|
||||
func NewTestSeal(t testing.T, opts *TestSealOpts) Seal {
|
||||
return NewDefaultSeal(shamirseal.NewSeal(opts.Logger))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/mitchellh/copystructure"
|
||||
|
||||
|
|
@ -837,6 +838,14 @@ func (c *TestCluster) UnsealCoresWithError() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *TestCluster) UnsealCore(t testing.T, core *TestClusterCore) {
|
||||
for _, key := range c.BarrierKeys {
|
||||
if _, err := core.Core.Unseal(TestKeyCopy(key)); err != nil {
|
||||
t.Fatalf("unseal err: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TestCluster) EnsureCoresSealed(t testing.T) {
|
||||
t.Helper()
|
||||
if err := c.ensureCoresSealed(); err != nil {
|
||||
|
|
@ -961,21 +970,23 @@ type TestListener struct {
|
|||
|
||||
type TestClusterCore struct {
|
||||
*Core
|
||||
CoreConfig *CoreConfig
|
||||
Client *api.Client
|
||||
Handler http.Handler
|
||||
Listeners []*TestListener
|
||||
ReloadFuncs *map[string][]reload.ReloadFunc
|
||||
ReloadFuncsLock *sync.RWMutex
|
||||
Server *http.Server
|
||||
ServerCert *x509.Certificate
|
||||
ServerCertBytes []byte
|
||||
ServerCertPEM []byte
|
||||
ServerKey *ecdsa.PrivateKey
|
||||
ServerKeyPEM []byte
|
||||
TLSConfig *tls.Config
|
||||
UnderlyingStorage physical.Backend
|
||||
Barrier SecurityBarrier
|
||||
CoreConfig *CoreConfig
|
||||
Client *api.Client
|
||||
Handler http.Handler
|
||||
Listeners []*TestListener
|
||||
ReloadFuncs *map[string][]reload.ReloadFunc
|
||||
ReloadFuncsLock *sync.RWMutex
|
||||
Server *http.Server
|
||||
ServerCert *x509.Certificate
|
||||
ServerCertBytes []byte
|
||||
ServerCertPEM []byte
|
||||
ServerKey *ecdsa.PrivateKey
|
||||
ServerKeyPEM []byte
|
||||
TLSConfig *tls.Config
|
||||
UnderlyingStorage physical.Backend
|
||||
UnderlyingRawStorage physical.Backend
|
||||
Barrier SecurityBarrier
|
||||
NodeID string
|
||||
}
|
||||
|
||||
type TestClusterOptions struct {
|
||||
|
|
@ -989,6 +1000,7 @@ type TestClusterOptions struct {
|
|||
TempDir string
|
||||
CACert []byte
|
||||
CAKey *ecdsa.PrivateKey
|
||||
PhysicalFactory func(hclog.Logger) (physical.Backend, error)
|
||||
FirstCoreNumber int
|
||||
}
|
||||
|
||||
|
|
@ -1330,13 +1342,13 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
|
|||
coreConfig.CounterSyncInterval = base.CounterSyncInterval
|
||||
}
|
||||
|
||||
if coreConfig.Physical == nil {
|
||||
if coreConfig.Physical == nil && (opts == nil || opts.PhysicalFactory == nil) {
|
||||
coreConfig.Physical, err = physInmem.NewInmem(nil, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if coreConfig.HAPhysical == nil {
|
||||
if coreConfig.HAPhysical == nil && (opts == nil || opts.PhysicalFactory == nil) {
|
||||
haPhys, err := physInmem.NewInmemHA(nil, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -1364,6 +1376,17 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
|
|||
localConfig.Logger = opts.Logger.Named(fmt.Sprintf("core%d", i))
|
||||
}
|
||||
|
||||
if opts != nil && opts.PhysicalFactory != nil {
|
||||
localConfig.Physical, err = opts.PhysicalFactory(localConfig.Logger)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if haPhysical, ok := localConfig.Physical.(physical.HABackend); ok {
|
||||
localConfig.HAPhysical = haPhysical
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case localConfig.LicensingConfig != nil:
|
||||
if pubKey != nil {
|
||||
|
|
@ -1571,19 +1594,21 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
|
|||
var ret []*TestClusterCore
|
||||
for i := 0; i < numCores; i++ {
|
||||
tcc := &TestClusterCore{
|
||||
Core: cores[i],
|
||||
CoreConfig: coreConfigs[i],
|
||||
ServerKey: certInfoSlice[i].key,
|
||||
ServerKeyPEM: certInfoSlice[i].keyPEM,
|
||||
ServerCert: certInfoSlice[i].cert,
|
||||
ServerCertBytes: certInfoSlice[i].certBytes,
|
||||
ServerCertPEM: certInfoSlice[i].certPEM,
|
||||
Listeners: listeners[i],
|
||||
Handler: handlers[i],
|
||||
Server: servers[i],
|
||||
TLSConfig: tlsConfigs[i],
|
||||
Client: getAPIClient(listeners[i][0].Address.Port, tlsConfigs[i]),
|
||||
Barrier: cores[i].barrier,
|
||||
Core: cores[i],
|
||||
CoreConfig: coreConfigs[i],
|
||||
ServerKey: certInfoSlice[i].key,
|
||||
ServerKeyPEM: certInfoSlice[i].keyPEM,
|
||||
ServerCert: certInfoSlice[i].cert,
|
||||
ServerCertBytes: certInfoSlice[i].certBytes,
|
||||
ServerCertPEM: certInfoSlice[i].certPEM,
|
||||
Listeners: listeners[i],
|
||||
Handler: handlers[i],
|
||||
Server: servers[i],
|
||||
TLSConfig: tlsConfigs[i],
|
||||
Client: getAPIClient(listeners[i][0].Address.Port, tlsConfigs[i]),
|
||||
Barrier: cores[i].barrier,
|
||||
NodeID: fmt.Sprintf("core-%d", i),
|
||||
UnderlyingRawStorage: coreConfigs[i].Physical,
|
||||
}
|
||||
tcc.ReloadFuncs = &cores[i].reloadFuncs
|
||||
tcc.ReloadFuncsLock = &cores[i].reloadFuncsLock
|
||||
|
|
|
|||
13
vendor/github.com/armon/go-metrics/.travis.yml
generated
vendored
Normal file
13
vendor/github.com/armon/go-metrics/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.x"
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
install:
|
||||
- go get ./...
|
||||
|
||||
script:
|
||||
- go test ./...
|
||||
16
vendor/github.com/armon/go-metrics/go.mod
generated
vendored
Normal file
16
vendor/github.com/armon/go-metrics/go.mod
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
module github.com/armon/go-metrics
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 // indirect
|
||||
github.com/pascaldekloe/goe v0.1.0
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/prometheus/client_golang v0.9.2
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||
)
|
||||
46
vendor/github.com/armon/go-metrics/go.sum
generated
vendored
Normal file
46
vendor/github.com/armon/go-metrics/go.sum
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4=
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3 h1:QlWt0KvWT0lq8MFppF9tsJGF+ynG7ztc2KIPhzRGk7s=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
4
vendor/github.com/armon/go-metrics/inmem.go
generated
vendored
4
vendor/github.com/armon/go-metrics/inmem.go
generated
vendored
|
|
@ -255,11 +255,11 @@ func (i *InmemSink) Data() []*IntervalMetrics {
|
|||
}
|
||||
copyCurrent.Counters = make(map[string]SampledValue, len(current.Counters))
|
||||
for k, v := range current.Counters {
|
||||
copyCurrent.Counters[k] = v
|
||||
copyCurrent.Counters[k] = v.deepCopy()
|
||||
}
|
||||
copyCurrent.Samples = make(map[string]SampledValue, len(current.Samples))
|
||||
for k, v := range current.Samples {
|
||||
copyCurrent.Samples[k] = v
|
||||
copyCurrent.Samples[k] = v.deepCopy()
|
||||
}
|
||||
current.RUnlock()
|
||||
|
||||
|
|
|
|||
17
vendor/github.com/armon/go-metrics/inmem_endpoint.go
generated
vendored
17
vendor/github.com/armon/go-metrics/inmem_endpoint.go
generated
vendored
|
|
@ -41,6 +41,16 @@ type SampledValue struct {
|
|||
DisplayLabels map[string]string `json:"Labels"`
|
||||
}
|
||||
|
||||
// deepCopy allocates a new instance of AggregateSample
|
||||
func (source *SampledValue) deepCopy() SampledValue {
|
||||
dest := *source
|
||||
if source.AggregateSample != nil {
|
||||
dest.AggregateSample = &AggregateSample{}
|
||||
*dest.AggregateSample = *source.AggregateSample
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
// DisplayMetrics returns a summary of the metrics from the most recent finished interval.
|
||||
func (i *InmemSink) DisplayMetrics(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
data := i.Data()
|
||||
|
|
@ -52,12 +62,15 @@ func (i *InmemSink) DisplayMetrics(resp http.ResponseWriter, req *http.Request)
|
|||
return nil, fmt.Errorf("no metric intervals have been initialized yet")
|
||||
case n == 1:
|
||||
// Show the current interval if it's all we have
|
||||
interval = i.intervals[0]
|
||||
interval = data[0]
|
||||
default:
|
||||
// Show the most recent finished interval if we have one
|
||||
interval = i.intervals[n-2]
|
||||
interval = data[n-2]
|
||||
}
|
||||
|
||||
interval.RLock()
|
||||
defer interval.RUnlock()
|
||||
|
||||
summary := MetricsSummary{
|
||||
Timestamp: interval.Interval.Round(time.Second).UTC().String(),
|
||||
Gauges: make([]GaugeValue, 0, len(interval.Gauges)),
|
||||
|
|
|
|||
2
vendor/github.com/armon/go-metrics/metrics.go
generated
vendored
2
vendor/github.com/armon/go-metrics/metrics.go
generated
vendored
|
|
@ -197,7 +197,7 @@ func (m *Metrics) filterLabels(labels []Label) []Label {
|
|||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
toReturn := labels[:0]
|
||||
toReturn := []Label{}
|
||||
for _, label := range labels {
|
||||
if m.labelIsAllowed(&label) {
|
||||
toReturn = append(toReturn, label)
|
||||
|
|
|
|||
8
vendor/github.com/circonus-labs/circonus-gometrics/CHANGELOG.md
generated
vendored
8
vendor/github.com/circonus-labs/circonus-gometrics/CHANGELOG.md
generated
vendored
|
|
@ -1,3 +1,11 @@
|
|||
# v2.3.1
|
||||
|
||||
* fix: incorrect attribute types in graph overlays (docs vs what api actually returns)
|
||||
|
||||
# v2.3.0
|
||||
|
||||
* fix: graph structures incorrectly represented nesting of overlay sets
|
||||
|
||||
# v2.2.7
|
||||
|
||||
* add: `search` (`*string`) attribute to graph datapoint
|
||||
|
|
|
|||
82
vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go
generated
vendored
82
vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go
generated
vendored
|
|
@ -86,37 +86,18 @@ type GraphMetricCluster struct {
|
|||
Stack *uint `json:"stack"` // uint or null
|
||||
}
|
||||
|
||||
// OverlayDataOptions defines overlay options for data. Note, each overlay type requires
|
||||
// a _subset_ of the options. See Graph API documentation (URL above) for details.
|
||||
type OverlayDataOptions struct {
|
||||
Alerts *int `json:"alerts,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
ArrayOutput *int `json:"array_output,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
BasePeriod *int `json:"base_period,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Delay *int `json:"delay,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Extension string `json:"extension,omitempty"` // string
|
||||
GraphTitle string `json:"graph_title,omitempty"` // string
|
||||
GraphUUID string `json:"graph_id,omitempty"` // string
|
||||
InPercent *bool `json:"in_percent,string,omitempty"` // boolean encoded as string BUG doc: boolean, api: string
|
||||
Inverse *int `json:"inverse,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Method string `json:"method,omitempty"` // string
|
||||
Model string `json:"model,omitempty"` // string
|
||||
ModelEnd string `json:"model_end,omitempty"` // string
|
||||
ModelPeriod string `json:"model_period,omitempty"` // string
|
||||
ModelRelative *int `json:"model_relative,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Out string `json:"out,omitempty"` // string
|
||||
Prequel string `json:"prequel,omitempty"` // string
|
||||
Presets string `json:"presets,omitempty"` // string
|
||||
Quantiles string `json:"quantiles,omitempty"` // string
|
||||
SeasonLength *int `json:"season_length,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Sensitivity *int `json:"sensitivity,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
SingleValue *int `json:"single_value,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
TargetPeriod string `json:"target_period,omitempty"` // string
|
||||
TimeOffset string `json:"time_offset,omitempty"` // string
|
||||
TimeShift *int `json:"time_shift,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Transform string `json:"transform,omitempty"` // string
|
||||
Version *int `json:"version,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Window *int `json:"window,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
XShift string `json:"x_shift,omitempty"` // string
|
||||
// GraphOverlaySet defines an overlay set for a graph
|
||||
type GraphOverlaySet struct {
|
||||
Overlays map[string]GraphOverlay `json:"overlays"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// GraphOverlay defines a single overlay in an overlay set
|
||||
type GraphOverlay struct {
|
||||
DataOpts OverlayDataOptions `json:"data_opts,omitempty"` // OverlayDataOptions
|
||||
ID string `json:"id,omitempty"` // string
|
||||
Title string `json:"title,omitempty"` // string
|
||||
UISpecs OverlayUISpecs `json:"ui_specs,omitempty"` // OverlayUISpecs
|
||||
}
|
||||
|
||||
// OverlayUISpecs defines UI specs for overlay
|
||||
|
|
@ -125,15 +106,40 @@ type OverlayUISpecs struct {
|
|||
ID string `json:"id,omitempty"` // string
|
||||
Label string `json:"label,omitempty"` // string
|
||||
Type string `json:"type,omitempty"` // string
|
||||
Z *int `json:"z,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Z string `json:"z,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
}
|
||||
|
||||
// GraphOverlaySet defines overlays for graph
|
||||
type GraphOverlaySet struct {
|
||||
DataOpts OverlayDataOptions `json:"data_opts,omitempty"` // OverlayDataOptions
|
||||
ID string `json:"id,omitempty"` // string
|
||||
Title string `json:"title,omitempty"` // string
|
||||
UISpecs OverlayUISpecs `json:"ui_specs,omitempty"` // OverlayUISpecs
|
||||
// OverlayDataOptions defines overlay options for data. Note, each overlay type requires
|
||||
// a _subset_ of the options. See Graph API documentation (URL above) for details.
|
||||
type OverlayDataOptions struct {
|
||||
Alerts string `json:"alerts,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
ArrayOutput string `json:"array_output,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
BasePeriod string `json:"base_period,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Delay string `json:"delay,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Extension string `json:"extension,omitempty"` // string
|
||||
GraphTitle string `json:"graph_title,omitempty"` // string
|
||||
GraphUUID string `json:"graph_id,omitempty"` // string
|
||||
InPercent string `json:"in_percent,omitempty"` // boolean encoded as string BUG doc: boolean, api: string
|
||||
Inverse string `json:"inverse,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Method string `json:"method,omitempty"` // string
|
||||
Model string `json:"model,omitempty"` // string
|
||||
ModelEnd string `json:"model_end,omitempty"` // string
|
||||
ModelPeriod string `json:"model_period,omitempty"` // string
|
||||
ModelRelative string `json:"model_relative,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Out string `json:"out,omitempty"` // string
|
||||
Prequel string `json:"prequel,omitempty"` // int
|
||||
Presets string `json:"presets,omitempty"` // string
|
||||
Quantiles string `json:"quantiles,omitempty"` // string
|
||||
SeasonLength string `json:"season_length,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Sensitivity string `json:"sensitivity,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
SingleValue string `json:"single_value,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
TargetPeriod string `json:"target_period,omitempty"` // string
|
||||
TimeOffset string `json:"time_offset,omitempty"` // string
|
||||
TimeShift string `json:"time_shift,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Transform string `json:"transform,omitempty"` // string
|
||||
Version string `json:"version,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
Window string `json:"window,omitempty"` // int encoded as string BUG doc: numeric, api: string
|
||||
XShift string `json:"x_shift,omitempty"` // string
|
||||
}
|
||||
|
||||
// Graph defines a graph. See https://login.circonus.com/resources/api/calls/graph for more information.
|
||||
|
|
|
|||
110
vendor/github.com/gogo/protobuf/gogoproto/gogo.pb.go
generated
vendored
110
vendor/github.com/gogo/protobuf/gogoproto/gogo.pb.go
generated
vendored
|
|
@ -1,12 +1,14 @@
|
|||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: gogo.proto
|
||||
|
||||
package gogoproto // import "github.com/gogo/protobuf/gogoproto"
|
||||
package gogoproto
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
|
|
@ -24,7 +26,7 @@ var E_GoprotoEnumPrefix = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 62001,
|
||||
Name: "gogoproto.goproto_enum_prefix",
|
||||
Tag: "varint,62001,opt,name=goproto_enum_prefix,json=goprotoEnumPrefix",
|
||||
Tag: "varint,62001,opt,name=goproto_enum_prefix",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +35,7 @@ var E_GoprotoEnumStringer = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 62021,
|
||||
Name: "gogoproto.goproto_enum_stringer",
|
||||
Tag: "varint,62021,opt,name=goproto_enum_stringer,json=goprotoEnumStringer",
|
||||
Tag: "varint,62021,opt,name=goproto_enum_stringer",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +44,7 @@ var E_EnumStringer = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 62022,
|
||||
Name: "gogoproto.enum_stringer",
|
||||
Tag: "varint,62022,opt,name=enum_stringer,json=enumStringer",
|
||||
Tag: "varint,62022,opt,name=enum_stringer",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +53,7 @@ var E_EnumCustomname = &proto.ExtensionDesc{
|
|||
ExtensionType: (*string)(nil),
|
||||
Field: 62023,
|
||||
Name: "gogoproto.enum_customname",
|
||||
Tag: "bytes,62023,opt,name=enum_customname,json=enumCustomname",
|
||||
Tag: "bytes,62023,opt,name=enum_customname",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +71,7 @@ var E_EnumvalueCustomname = &proto.ExtensionDesc{
|
|||
ExtensionType: (*string)(nil),
|
||||
Field: 66001,
|
||||
Name: "gogoproto.enumvalue_customname",
|
||||
Tag: "bytes,66001,opt,name=enumvalue_customname,json=enumvalueCustomname",
|
||||
Tag: "bytes,66001,opt,name=enumvalue_customname",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +80,7 @@ var E_GoprotoGettersAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63001,
|
||||
Name: "gogoproto.goproto_getters_all",
|
||||
Tag: "varint,63001,opt,name=goproto_getters_all,json=goprotoGettersAll",
|
||||
Tag: "varint,63001,opt,name=goproto_getters_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +89,7 @@ var E_GoprotoEnumPrefixAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63002,
|
||||
Name: "gogoproto.goproto_enum_prefix_all",
|
||||
Tag: "varint,63002,opt,name=goproto_enum_prefix_all,json=goprotoEnumPrefixAll",
|
||||
Tag: "varint,63002,opt,name=goproto_enum_prefix_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +98,7 @@ var E_GoprotoStringerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63003,
|
||||
Name: "gogoproto.goproto_stringer_all",
|
||||
Tag: "varint,63003,opt,name=goproto_stringer_all,json=goprotoStringerAll",
|
||||
Tag: "varint,63003,opt,name=goproto_stringer_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +107,7 @@ var E_VerboseEqualAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63004,
|
||||
Name: "gogoproto.verbose_equal_all",
|
||||
Tag: "varint,63004,opt,name=verbose_equal_all,json=verboseEqualAll",
|
||||
Tag: "varint,63004,opt,name=verbose_equal_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +116,7 @@ var E_FaceAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63005,
|
||||
Name: "gogoproto.face_all",
|
||||
Tag: "varint,63005,opt,name=face_all,json=faceAll",
|
||||
Tag: "varint,63005,opt,name=face_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +125,7 @@ var E_GostringAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63006,
|
||||
Name: "gogoproto.gostring_all",
|
||||
Tag: "varint,63006,opt,name=gostring_all,json=gostringAll",
|
||||
Tag: "varint,63006,opt,name=gostring_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +134,7 @@ var E_PopulateAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63007,
|
||||
Name: "gogoproto.populate_all",
|
||||
Tag: "varint,63007,opt,name=populate_all,json=populateAll",
|
||||
Tag: "varint,63007,opt,name=populate_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +143,7 @@ var E_StringerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63008,
|
||||
Name: "gogoproto.stringer_all",
|
||||
Tag: "varint,63008,opt,name=stringer_all,json=stringerAll",
|
||||
Tag: "varint,63008,opt,name=stringer_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ var E_OnlyoneAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63009,
|
||||
Name: "gogoproto.onlyone_all",
|
||||
Tag: "varint,63009,opt,name=onlyone_all,json=onlyoneAll",
|
||||
Tag: "varint,63009,opt,name=onlyone_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +161,7 @@ var E_EqualAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63013,
|
||||
Name: "gogoproto.equal_all",
|
||||
Tag: "varint,63013,opt,name=equal_all,json=equalAll",
|
||||
Tag: "varint,63013,opt,name=equal_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +170,7 @@ var E_DescriptionAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63014,
|
||||
Name: "gogoproto.description_all",
|
||||
Tag: "varint,63014,opt,name=description_all,json=descriptionAll",
|
||||
Tag: "varint,63014,opt,name=description_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +179,7 @@ var E_TestgenAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63015,
|
||||
Name: "gogoproto.testgen_all",
|
||||
Tag: "varint,63015,opt,name=testgen_all,json=testgenAll",
|
||||
Tag: "varint,63015,opt,name=testgen_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +188,7 @@ var E_BenchgenAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63016,
|
||||
Name: "gogoproto.benchgen_all",
|
||||
Tag: "varint,63016,opt,name=benchgen_all,json=benchgenAll",
|
||||
Tag: "varint,63016,opt,name=benchgen_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +197,7 @@ var E_MarshalerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63017,
|
||||
Name: "gogoproto.marshaler_all",
|
||||
Tag: "varint,63017,opt,name=marshaler_all,json=marshalerAll",
|
||||
Tag: "varint,63017,opt,name=marshaler_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +206,7 @@ var E_UnmarshalerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63018,
|
||||
Name: "gogoproto.unmarshaler_all",
|
||||
Tag: "varint,63018,opt,name=unmarshaler_all,json=unmarshalerAll",
|
||||
Tag: "varint,63018,opt,name=unmarshaler_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +215,7 @@ var E_StableMarshalerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63019,
|
||||
Name: "gogoproto.stable_marshaler_all",
|
||||
Tag: "varint,63019,opt,name=stable_marshaler_all,json=stableMarshalerAll",
|
||||
Tag: "varint,63019,opt,name=stable_marshaler_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +224,7 @@ var E_SizerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63020,
|
||||
Name: "gogoproto.sizer_all",
|
||||
Tag: "varint,63020,opt,name=sizer_all,json=sizerAll",
|
||||
Tag: "varint,63020,opt,name=sizer_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +233,7 @@ var E_GoprotoEnumStringerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63021,
|
||||
Name: "gogoproto.goproto_enum_stringer_all",
|
||||
Tag: "varint,63021,opt,name=goproto_enum_stringer_all,json=goprotoEnumStringerAll",
|
||||
Tag: "varint,63021,opt,name=goproto_enum_stringer_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +242,7 @@ var E_EnumStringerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63022,
|
||||
Name: "gogoproto.enum_stringer_all",
|
||||
Tag: "varint,63022,opt,name=enum_stringer_all,json=enumStringerAll",
|
||||
Tag: "varint,63022,opt,name=enum_stringer_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +251,7 @@ var E_UnsafeMarshalerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63023,
|
||||
Name: "gogoproto.unsafe_marshaler_all",
|
||||
Tag: "varint,63023,opt,name=unsafe_marshaler_all,json=unsafeMarshalerAll",
|
||||
Tag: "varint,63023,opt,name=unsafe_marshaler_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +260,7 @@ var E_UnsafeUnmarshalerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63024,
|
||||
Name: "gogoproto.unsafe_unmarshaler_all",
|
||||
Tag: "varint,63024,opt,name=unsafe_unmarshaler_all,json=unsafeUnmarshalerAll",
|
||||
Tag: "varint,63024,opt,name=unsafe_unmarshaler_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -267,7 +269,7 @@ var E_GoprotoExtensionsMapAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63025,
|
||||
Name: "gogoproto.goproto_extensions_map_all",
|
||||
Tag: "varint,63025,opt,name=goproto_extensions_map_all,json=goprotoExtensionsMapAll",
|
||||
Tag: "varint,63025,opt,name=goproto_extensions_map_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +278,7 @@ var E_GoprotoUnrecognizedAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63026,
|
||||
Name: "gogoproto.goproto_unrecognized_all",
|
||||
Tag: "varint,63026,opt,name=goproto_unrecognized_all,json=goprotoUnrecognizedAll",
|
||||
Tag: "varint,63026,opt,name=goproto_unrecognized_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +287,7 @@ var E_GogoprotoImport = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63027,
|
||||
Name: "gogoproto.gogoproto_import",
|
||||
Tag: "varint,63027,opt,name=gogoproto_import,json=gogoprotoImport",
|
||||
Tag: "varint,63027,opt,name=gogoproto_import",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -294,7 +296,7 @@ var E_ProtosizerAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63028,
|
||||
Name: "gogoproto.protosizer_all",
|
||||
Tag: "varint,63028,opt,name=protosizer_all,json=protosizerAll",
|
||||
Tag: "varint,63028,opt,name=protosizer_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +305,7 @@ var E_CompareAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63029,
|
||||
Name: "gogoproto.compare_all",
|
||||
Tag: "varint,63029,opt,name=compare_all,json=compareAll",
|
||||
Tag: "varint,63029,opt,name=compare_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -312,7 +314,7 @@ var E_TypedeclAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63030,
|
||||
Name: "gogoproto.typedecl_all",
|
||||
Tag: "varint,63030,opt,name=typedecl_all,json=typedeclAll",
|
||||
Tag: "varint,63030,opt,name=typedecl_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +323,7 @@ var E_EnumdeclAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63031,
|
||||
Name: "gogoproto.enumdecl_all",
|
||||
Tag: "varint,63031,opt,name=enumdecl_all,json=enumdeclAll",
|
||||
Tag: "varint,63031,opt,name=enumdecl_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +332,7 @@ var E_GoprotoRegistration = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63032,
|
||||
Name: "gogoproto.goproto_registration",
|
||||
Tag: "varint,63032,opt,name=goproto_registration,json=goprotoRegistration",
|
||||
Tag: "varint,63032,opt,name=goproto_registration",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -339,7 +341,7 @@ var E_MessagenameAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63033,
|
||||
Name: "gogoproto.messagename_all",
|
||||
Tag: "varint,63033,opt,name=messagename_all,json=messagenameAll",
|
||||
Tag: "varint,63033,opt,name=messagename_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +350,7 @@ var E_GoprotoSizecacheAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63034,
|
||||
Name: "gogoproto.goproto_sizecache_all",
|
||||
Tag: "varint,63034,opt,name=goproto_sizecache_all,json=goprotoSizecacheAll",
|
||||
Tag: "varint,63034,opt,name=goproto_sizecache_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -357,7 +359,7 @@ var E_GoprotoUnkeyedAll = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 63035,
|
||||
Name: "gogoproto.goproto_unkeyed_all",
|
||||
Tag: "varint,63035,opt,name=goproto_unkeyed_all,json=goprotoUnkeyedAll",
|
||||
Tag: "varint,63035,opt,name=goproto_unkeyed_all",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -366,7 +368,7 @@ var E_GoprotoGetters = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64001,
|
||||
Name: "gogoproto.goproto_getters",
|
||||
Tag: "varint,64001,opt,name=goproto_getters,json=goprotoGetters",
|
||||
Tag: "varint,64001,opt,name=goproto_getters",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +377,7 @@ var E_GoprotoStringer = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64003,
|
||||
Name: "gogoproto.goproto_stringer",
|
||||
Tag: "varint,64003,opt,name=goproto_stringer,json=goprotoStringer",
|
||||
Tag: "varint,64003,opt,name=goproto_stringer",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +386,7 @@ var E_VerboseEqual = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64004,
|
||||
Name: "gogoproto.verbose_equal",
|
||||
Tag: "varint,64004,opt,name=verbose_equal,json=verboseEqual",
|
||||
Tag: "varint,64004,opt,name=verbose_equal",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -492,7 +494,7 @@ var E_StableMarshaler = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64019,
|
||||
Name: "gogoproto.stable_marshaler",
|
||||
Tag: "varint,64019,opt,name=stable_marshaler,json=stableMarshaler",
|
||||
Tag: "varint,64019,opt,name=stable_marshaler",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -510,7 +512,7 @@ var E_UnsafeMarshaler = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64023,
|
||||
Name: "gogoproto.unsafe_marshaler",
|
||||
Tag: "varint,64023,opt,name=unsafe_marshaler,json=unsafeMarshaler",
|
||||
Tag: "varint,64023,opt,name=unsafe_marshaler",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -519,7 +521,7 @@ var E_UnsafeUnmarshaler = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64024,
|
||||
Name: "gogoproto.unsafe_unmarshaler",
|
||||
Tag: "varint,64024,opt,name=unsafe_unmarshaler,json=unsafeUnmarshaler",
|
||||
Tag: "varint,64024,opt,name=unsafe_unmarshaler",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -528,7 +530,7 @@ var E_GoprotoExtensionsMap = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64025,
|
||||
Name: "gogoproto.goproto_extensions_map",
|
||||
Tag: "varint,64025,opt,name=goproto_extensions_map,json=goprotoExtensionsMap",
|
||||
Tag: "varint,64025,opt,name=goproto_extensions_map",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -537,7 +539,7 @@ var E_GoprotoUnrecognized = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64026,
|
||||
Name: "gogoproto.goproto_unrecognized",
|
||||
Tag: "varint,64026,opt,name=goproto_unrecognized,json=goprotoUnrecognized",
|
||||
Tag: "varint,64026,opt,name=goproto_unrecognized",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -582,7 +584,7 @@ var E_GoprotoSizecache = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64034,
|
||||
Name: "gogoproto.goproto_sizecache",
|
||||
Tag: "varint,64034,opt,name=goproto_sizecache,json=goprotoSizecache",
|
||||
Tag: "varint,64034,opt,name=goproto_sizecache",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -591,7 +593,7 @@ var E_GoprotoUnkeyed = &proto.ExtensionDesc{
|
|||
ExtensionType: (*bool)(nil),
|
||||
Field: 64035,
|
||||
Name: "gogoproto.goproto_unkeyed",
|
||||
Tag: "varint,64035,opt,name=goproto_unkeyed,json=goprotoUnkeyed",
|
||||
Tag: "varint,64035,opt,name=goproto_unkeyed",
|
||||
Filename: "gogo.proto",
|
||||
}
|
||||
|
||||
|
|
@ -782,9 +784,9 @@ func init() {
|
|||
proto.RegisterExtension(E_Wktpointer)
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("gogo.proto", fileDescriptor_gogo_b95f77e237336c7c) }
|
||||
func init() { proto.RegisterFile("gogo.proto", fileDescriptor_592445b5231bc2b9) }
|
||||
|
||||
var fileDescriptor_gogo_b95f77e237336c7c = []byte{
|
||||
var fileDescriptor_592445b5231bc2b9 = []byte{
|
||||
// 1328 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x98, 0x49, 0x6f, 0x1c, 0x45,
|
||||
0x14, 0x80, 0x85, 0x48, 0x64, 0x4f, 0x79, 0x8b, 0xc7, 0xc6, 0x84, 0x08, 0x44, 0xe0, 0xc4, 0xc9,
|
||||
|
|
|
|||
102
vendor/github.com/gogo/protobuf/io/full.go
generated
vendored
Normal file
102
vendor/github.com/gogo/protobuf/io/full.go
generated
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Protocol Buffers for Go with Gadgets
|
||||
//
|
||||
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
|
||||
// http://github.com/gogo/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"io"
|
||||
)
|
||||
|
||||
func NewFullWriter(w io.Writer) WriteCloser {
|
||||
return &fullWriter{w, nil}
|
||||
}
|
||||
|
||||
type fullWriter struct {
|
||||
w io.Writer
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (this *fullWriter) WriteMsg(msg proto.Message) (err error) {
|
||||
var data []byte
|
||||
if m, ok := msg.(marshaler); ok {
|
||||
n, ok := getSize(m)
|
||||
if !ok {
|
||||
data, err = proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if n >= len(this.buffer) {
|
||||
this.buffer = make([]byte, n)
|
||||
}
|
||||
_, err = m.MarshalTo(this.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = this.buffer[:n]
|
||||
} else {
|
||||
data, err = proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = this.w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *fullWriter) Close() error {
|
||||
if closer, ok := this.w.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fullReader struct {
|
||||
r io.Reader
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func NewFullReader(r io.Reader, maxSize int) ReadCloser {
|
||||
return &fullReader{r, make([]byte, maxSize)}
|
||||
}
|
||||
|
||||
func (this *fullReader) ReadMsg(msg proto.Message) error {
|
||||
length, err := this.r.Read(this.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return proto.Unmarshal(this.buf[:length], msg)
|
||||
}
|
||||
|
||||
func (this *fullReader) Close() error {
|
||||
if closer, ok := this.r.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
70
vendor/github.com/gogo/protobuf/io/io.go
generated
vendored
Normal file
70
vendor/github.com/gogo/protobuf/io/io.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Protocol Buffers for Go with Gadgets
|
||||
//
|
||||
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
|
||||
// http://github.com/gogo/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Writer interface {
|
||||
WriteMsg(proto.Message) error
|
||||
}
|
||||
|
||||
type WriteCloser interface {
|
||||
Writer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type Reader interface {
|
||||
ReadMsg(msg proto.Message) error
|
||||
}
|
||||
|
||||
type ReadCloser interface {
|
||||
Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type marshaler interface {
|
||||
MarshalTo(data []byte) (n int, err error)
|
||||
}
|
||||
|
||||
func getSize(v interface{}) (int, bool) {
|
||||
if sz, ok := v.(interface {
|
||||
Size() (n int)
|
||||
}); ok {
|
||||
return sz.Size(), true
|
||||
} else if sz, ok := v.(interface {
|
||||
ProtoSize() (n int)
|
||||
}); ok {
|
||||
return sz.ProtoSize(), true
|
||||
} else {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
138
vendor/github.com/gogo/protobuf/io/uint32.go
generated
vendored
Normal file
138
vendor/github.com/gogo/protobuf/io/uint32.go
generated
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// Protocol Buffers for Go with Gadgets
|
||||
//
|
||||
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
|
||||
// http://github.com/gogo/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
const uint32BinaryLen = 4
|
||||
|
||||
func NewUint32DelimitedWriter(w io.Writer, byteOrder binary.ByteOrder) WriteCloser {
|
||||
return &uint32Writer{w, byteOrder, nil, make([]byte, uint32BinaryLen)}
|
||||
}
|
||||
|
||||
func NewSizeUint32DelimitedWriter(w io.Writer, byteOrder binary.ByteOrder, size int) WriteCloser {
|
||||
return &uint32Writer{w, byteOrder, make([]byte, size), make([]byte, uint32BinaryLen)}
|
||||
}
|
||||
|
||||
type uint32Writer struct {
|
||||
w io.Writer
|
||||
byteOrder binary.ByteOrder
|
||||
buffer []byte
|
||||
lenBuf []byte
|
||||
}
|
||||
|
||||
func (this *uint32Writer) writeFallback(msg proto.Message) error {
|
||||
data, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
length := uint32(len(data))
|
||||
this.byteOrder.PutUint32(this.lenBuf, length)
|
||||
if _, err = this.w.Write(this.lenBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = this.w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *uint32Writer) WriteMsg(msg proto.Message) error {
|
||||
m, ok := msg.(marshaler)
|
||||
if !ok {
|
||||
return this.writeFallback(msg)
|
||||
}
|
||||
|
||||
n, ok := getSize(m)
|
||||
if !ok {
|
||||
return this.writeFallback(msg)
|
||||
}
|
||||
|
||||
size := n + uint32BinaryLen
|
||||
if size > len(this.buffer) {
|
||||
this.buffer = make([]byte, size)
|
||||
}
|
||||
|
||||
this.byteOrder.PutUint32(this.buffer, uint32(n))
|
||||
if _, err := m.MarshalTo(this.buffer[uint32BinaryLen:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := this.w.Write(this.buffer[:size])
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *uint32Writer) Close() error {
|
||||
if closer, ok := this.w.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uint32Reader struct {
|
||||
r io.Reader
|
||||
byteOrder binary.ByteOrder
|
||||
lenBuf []byte
|
||||
buf []byte
|
||||
maxSize int
|
||||
}
|
||||
|
||||
func NewUint32DelimitedReader(r io.Reader, byteOrder binary.ByteOrder, maxSize int) ReadCloser {
|
||||
return &uint32Reader{r, byteOrder, make([]byte, 4), nil, maxSize}
|
||||
}
|
||||
|
||||
func (this *uint32Reader) ReadMsg(msg proto.Message) error {
|
||||
if _, err := io.ReadFull(this.r, this.lenBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
length32 := this.byteOrder.Uint32(this.lenBuf)
|
||||
length := int(length32)
|
||||
if length < 0 || length > this.maxSize {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
if length >= len(this.buf) {
|
||||
this.buf = make([]byte, length)
|
||||
}
|
||||
_, err := io.ReadFull(this.r, this.buf[:length])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return proto.Unmarshal(this.buf[:length], msg)
|
||||
}
|
||||
|
||||
func (this *uint32Reader) Close() error {
|
||||
if closer, ok := this.r.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
133
vendor/github.com/gogo/protobuf/io/varint.go
generated
vendored
Normal file
133
vendor/github.com/gogo/protobuf/io/varint.go
generated
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Protocol Buffers for Go with Gadgets
|
||||
//
|
||||
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
|
||||
// http://github.com/gogo/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package io
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
errSmallBuffer = errors.New("Buffer Too Small")
|
||||
errLargeValue = errors.New("Value is Larger than 64 bits")
|
||||
)
|
||||
|
||||
func NewDelimitedWriter(w io.Writer) WriteCloser {
|
||||
return &varintWriter{w, make([]byte, binary.MaxVarintLen64), nil}
|
||||
}
|
||||
|
||||
type varintWriter struct {
|
||||
w io.Writer
|
||||
lenBuf []byte
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (this *varintWriter) WriteMsg(msg proto.Message) (err error) {
|
||||
var data []byte
|
||||
if m, ok := msg.(marshaler); ok {
|
||||
n, ok := getSize(m)
|
||||
if ok {
|
||||
if n+binary.MaxVarintLen64 >= len(this.buffer) {
|
||||
this.buffer = make([]byte, n+binary.MaxVarintLen64)
|
||||
}
|
||||
lenOff := binary.PutUvarint(this.buffer, uint64(n))
|
||||
_, err = m.MarshalTo(this.buffer[lenOff:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = this.w.Write(this.buffer[:lenOff+n])
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
data, err = proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
length := uint64(len(data))
|
||||
n := binary.PutUvarint(this.lenBuf, length)
|
||||
_, err = this.w.Write(this.lenBuf[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = this.w.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *varintWriter) Close() error {
|
||||
if closer, ok := this.w.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDelimitedReader(r io.Reader, maxSize int) ReadCloser {
|
||||
var closer io.Closer
|
||||
if c, ok := r.(io.Closer); ok {
|
||||
closer = c
|
||||
}
|
||||
return &varintReader{bufio.NewReader(r), nil, maxSize, closer}
|
||||
}
|
||||
|
||||
type varintReader struct {
|
||||
r *bufio.Reader
|
||||
buf []byte
|
||||
maxSize int
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func (this *varintReader) ReadMsg(msg proto.Message) error {
|
||||
length64, err := binary.ReadUvarint(this.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
length := int(length64)
|
||||
if length < 0 || length > this.maxSize {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
if len(this.buf) < length {
|
||||
this.buf = make([]byte, length)
|
||||
}
|
||||
buf := this.buf[:length]
|
||||
if _, err := io.ReadFull(this.r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return proto.Unmarshal(buf, msg)
|
||||
}
|
||||
|
||||
func (this *varintReader) Close() error {
|
||||
if this.closer != nil {
|
||||
return this.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
vendor/github.com/gogo/protobuf/proto/decode.go
generated
vendored
1
vendor/github.com/gogo/protobuf/proto/decode.go
generated
vendored
|
|
@ -186,7 +186,6 @@ func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
|||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
// x -= 0x80 << 63 // Always zero.
|
||||
|
||||
return 0, errOverflow
|
||||
|
||||
|
|
|
|||
63
vendor/github.com/gogo/protobuf/proto/deprecated.go
generated
vendored
Normal file
63
vendor/github.com/gogo/protobuf/proto/deprecated.go
generated
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import "errors"
|
||||
|
||||
// Deprecated: do not use.
|
||||
type Stats struct{ Emalloc, Dmalloc, Encode, Decode, Chit, Cmiss, Size uint64 }
|
||||
|
||||
// Deprecated: do not use.
|
||||
func GetStats() Stats { return Stats{} }
|
||||
|
||||
// Deprecated: do not use.
|
||||
func MarshalMessageSet(interface{}) ([]byte, error) {
|
||||
return nil, errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func UnmarshalMessageSet([]byte, interface{}) error {
|
||||
return errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func MarshalMessageSetJSON(interface{}) ([]byte, error) {
|
||||
return nil, errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func UnmarshalMessageSetJSON([]byte, interface{}) error {
|
||||
return errors.New("proto: not implemented")
|
||||
}
|
||||
|
||||
// Deprecated: do not use.
|
||||
func RegisterMessageSetType(Message, int32, string) {}
|
||||
2
vendor/github.com/gogo/protobuf/proto/extensions.go
generated
vendored
2
vendor/github.com/gogo/protobuf/proto/extensions.go
generated
vendored
|
|
@ -544,7 +544,7 @@ func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error
|
|||
}
|
||||
typ := reflect.TypeOf(extension.ExtensionType)
|
||||
if typ != reflect.TypeOf(value) {
|
||||
return errors.New("proto: bad extension value type")
|
||||
return fmt.Errorf("proto: bad extension value type. got: %T, want: %T", value, extension.ExtensionType)
|
||||
}
|
||||
// nil extension values need to be caught early, because the
|
||||
// encoder can't distinguish an ErrNil due to a nil extension
|
||||
|
|
|
|||
20
vendor/github.com/gogo/protobuf/proto/lib.go
generated
vendored
20
vendor/github.com/gogo/protobuf/proto/lib.go
generated
vendored
|
|
@ -341,26 +341,6 @@ type Message interface {
|
|||
ProtoMessage()
|
||||
}
|
||||
|
||||
// Stats records allocation details about the protocol buffer encoders
|
||||
// and decoders. Useful for tuning the library itself.
|
||||
type Stats struct {
|
||||
Emalloc uint64 // mallocs in encode
|
||||
Dmalloc uint64 // mallocs in decode
|
||||
Encode uint64 // number of encodes
|
||||
Decode uint64 // number of decodes
|
||||
Chit uint64 // number of cache hits
|
||||
Cmiss uint64 // number of cache misses
|
||||
Size uint64 // number of sizes
|
||||
}
|
||||
|
||||
// Set to true to enable stats collection.
|
||||
const collectStats = false
|
||||
|
||||
var stats Stats
|
||||
|
||||
// GetStats returns a copy of the global Stats structure.
|
||||
func GetStats() Stats { return stats }
|
||||
|
||||
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||
// protocol buffers. It may be reused between invocations to
|
||||
// reduce memory usage. It is not necessary to use a Buffer;
|
||||
|
|
|
|||
137
vendor/github.com/gogo/protobuf/proto/message_set.go
generated
vendored
137
vendor/github.com/gogo/protobuf/proto/message_set.go
generated
vendored
|
|
@ -36,13 +36,7 @@ package proto
|
|||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
||||
|
|
@ -145,46 +139,9 @@ func skipVarint(buf []byte) []byte {
|
|||
return buf[i+1:]
|
||||
}
|
||||
|
||||
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSet(exts interface{}) ([]byte, error) {
|
||||
return marshalMessageSet(exts, false)
|
||||
}
|
||||
|
||||
// marshaMessageSet implements above function, with the opt to turn on / off deterministic during Marshal.
|
||||
func marshalMessageSet(exts interface{}, deterministic bool) ([]byte, error) {
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
var u marshalInfo
|
||||
siz := u.sizeMessageSet(exts)
|
||||
b := make([]byte, 0, siz)
|
||||
return u.appendMessageSet(b, exts, deterministic)
|
||||
|
||||
case map[int32]Extension:
|
||||
// This is an old-style extension map.
|
||||
// Wrap it in a new-style XXX_InternalExtensions.
|
||||
ie := XXX_InternalExtensions{
|
||||
p: &struct {
|
||||
mu sync.Mutex
|
||||
extensionMap map[int32]Extension
|
||||
}{
|
||||
extensionMap: exts,
|
||||
},
|
||||
}
|
||||
|
||||
var u marshalInfo
|
||||
siz := u.sizeMessageSet(&ie)
|
||||
b := make([]byte, 0, siz)
|
||||
return u.appendMessageSet(b, &ie, deterministic)
|
||||
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// unmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// It is called by Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||
func unmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
|
|
@ -222,93 +179,3 @@ func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSetJSON(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
var mu sync.Locker
|
||||
m, mu = exts.extensionsRead()
|
||||
if m != nil {
|
||||
// Keep the extensions map locked until we're done marshaling to prevent
|
||||
// races between marshaling and unmarshaling the lazily-{en,de}coded
|
||||
// values.
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
}
|
||||
case map[int32]Extension:
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
|
||||
// Process the map in key order for deterministic output.
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||
|
||||
for i, id := range ids {
|
||||
ext := m[id]
|
||||
msd, ok := messageSetMap[id]
|
||||
if !ok {
|
||||
// Unknown type; we can't render it, so skip it.
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 && b.Len() > 1 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
|
||||
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||
|
||||
x := ext.value
|
||||
if x == nil {
|
||||
x = reflect.New(msd.t.Elem()).Interface()
|
||||
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(d)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSetJSON(buf []byte, exts interface{}) error {
|
||||
// Common-case fast path.
|
||||
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is fairly tricky, and it's not clear that it is needed.
|
||||
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||
}
|
||||
|
||||
// A global registry of types that can be used in a MessageSet.
|
||||
|
||||
var messageSetMap = make(map[int32]messageSetDesc)
|
||||
|
||||
type messageSetDesc struct {
|
||||
t reflect.Type // pointer to struct
|
||||
name string
|
||||
}
|
||||
|
||||
// RegisterMessageSetType is called from the generated code.
|
||||
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||
messageSetMap[fieldNum] = messageSetDesc{
|
||||
t: reflect.TypeOf(m),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
vendor/github.com/gogo/protobuf/proto/properties.go
generated
vendored
9
vendor/github.com/gogo/protobuf/proto/properties.go
generated
vendored
|
|
@ -391,9 +391,6 @@ func GetProperties(t reflect.Type) *StructProperties {
|
|||
sprop, ok := propertiesMap[t]
|
||||
propertiesMu.RUnlock()
|
||||
if ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return sprop
|
||||
}
|
||||
|
||||
|
|
@ -406,14 +403,8 @@ func GetProperties(t reflect.Type) *StructProperties {
|
|||
// getPropertiesLocked requires that propertiesMu is held.
|
||||
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||
if prop, ok := propertiesMap[t]; ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return prop
|
||||
}
|
||||
if collectStats {
|
||||
stats.Cmiss++
|
||||
}
|
||||
|
||||
prop := new(StructProperties)
|
||||
// in case of recursive protos, fill this in now.
|
||||
|
|
|
|||
2
vendor/github.com/gogo/protobuf/proto/table_marshal.go
generated
vendored
2
vendor/github.com/gogo/protobuf/proto/table_marshal.go
generated
vendored
|
|
@ -491,7 +491,7 @@ func (fi *marshalFieldInfo) computeMarshalFieldInfo(f *reflect.StructField) {
|
|||
|
||||
func (fi *marshalFieldInfo) computeOneofFieldInfo(f *reflect.StructField, oneofImplementers []interface{}) {
|
||||
fi.field = toField(f)
|
||||
fi.wiretag = 1<<31 - 1 // Use a large tag number, make oneofs sorted at the end. This tag will not appear on the wire.
|
||||
fi.wiretag = math.MaxInt32 // Use a large tag number, make oneofs sorted at the end. This tag will not appear on the wire.
|
||||
fi.isPointer = true
|
||||
fi.sizer, fi.marshaler = makeOneOfMarshaler(fi, f)
|
||||
fi.oneofElems = make(map[reflect.Type]*marshalElemInfo)
|
||||
|
|
|
|||
4
vendor/github.com/gogo/protobuf/proto/table_unmarshal.go
generated
vendored
4
vendor/github.com/gogo/protobuf/proto/table_unmarshal.go
generated
vendored
|
|
@ -138,7 +138,7 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
|
|||
u.computeUnmarshalInfo()
|
||||
}
|
||||
if u.isMessageSet {
|
||||
return UnmarshalMessageSet(b, m.offset(u.extensions).toExtensions())
|
||||
return unmarshalMessageSet(b, m.offset(u.extensions).toExtensions())
|
||||
}
|
||||
var reqMask uint64 // bitmask of required fields we've seen.
|
||||
var errLater error
|
||||
|
|
@ -2142,7 +2142,7 @@ func encodeVarint(b []byte, x uint64) []byte {
|
|||
// If there is an error, it returns 0,0.
|
||||
func decodeVarint(b []byte) (uint64, int) {
|
||||
var x, y uint64
|
||||
if len(b) <= 0 {
|
||||
if len(b) == 0 {
|
||||
goto bad
|
||||
}
|
||||
x = uint64(b[0])
|
||||
|
|
|
|||
565
vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor.pb.go
generated
vendored
565
vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor.pb.go
generated
vendored
File diff suppressed because it is too large
Load diff
26
vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor_gostring.gen.go
generated
vendored
26
vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor_gostring.gen.go
generated
vendored
|
|
@ -3,14 +3,16 @@
|
|||
|
||||
package descriptor
|
||||
|
||||
import fmt "fmt"
|
||||
import strings "strings"
|
||||
import github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
|
||||
import sort "sort"
|
||||
import strconv "strconv"
|
||||
import reflect "reflect"
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import math "math"
|
||||
import (
|
||||
fmt "fmt"
|
||||
github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
math "math"
|
||||
reflect "reflect"
|
||||
sort "sort"
|
||||
strconv "strconv"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
|
|
@ -358,7 +360,7 @@ func (this *FileOptions) GoString() string {
|
|||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := make([]string, 0, 23)
|
||||
s := make([]string, 0, 25)
|
||||
s = append(s, "&descriptor.FileOptions{")
|
||||
if this.JavaPackage != nil {
|
||||
s = append(s, "JavaPackage: "+valueToGoStringDescriptor(this.JavaPackage, "string")+",\n")
|
||||
|
|
@ -414,6 +416,12 @@ func (this *FileOptions) GoString() string {
|
|||
if this.PhpNamespace != nil {
|
||||
s = append(s, "PhpNamespace: "+valueToGoStringDescriptor(this.PhpNamespace, "string")+",\n")
|
||||
}
|
||||
if this.PhpMetadataNamespace != nil {
|
||||
s = append(s, "PhpMetadataNamespace: "+valueToGoStringDescriptor(this.PhpMetadataNamespace, "string")+",\n")
|
||||
}
|
||||
if this.RubyPackage != nil {
|
||||
s = append(s, "RubyPackage: "+valueToGoStringDescriptor(this.RubyPackage, "string")+",\n")
|
||||
}
|
||||
if this.UninterpretedOption != nil {
|
||||
s = append(s, "UninterpretedOption: "+fmt.Sprintf("%#v", this.UninterpretedOption)+",\n")
|
||||
}
|
||||
|
|
|
|||
25
vendor/github.com/hashicorp/go-msgpack/LICENSE
generated
vendored
Normal file
25
vendor/github.com/hashicorp/go-msgpack/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2012, 2013 Ugorji Nwoke.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
143
vendor/github.com/hashicorp/go-msgpack/codec/0doc.go
generated
vendored
Normal file
143
vendor/github.com/hashicorp/go-msgpack/codec/0doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
/*
|
||||
High Performance, Feature-Rich Idiomatic Go encoding library for msgpack and binc .
|
||||
|
||||
Supported Serialization formats are:
|
||||
|
||||
- msgpack: [https://github.com/msgpack/msgpack]
|
||||
- binc: [http://github.com/ugorji/binc]
|
||||
|
||||
To install:
|
||||
|
||||
go get github.com/ugorji/go/codec
|
||||
|
||||
The idiomatic Go support is as seen in other encoding packages in
|
||||
the standard library (ie json, xml, gob, etc).
|
||||
|
||||
Rich Feature Set includes:
|
||||
|
||||
- Simple but extremely powerful and feature-rich API
|
||||
- Very High Performance.
|
||||
Our extensive benchmarks show us outperforming Gob, Json and Bson by 2-4X.
|
||||
This was achieved by taking extreme care on:
|
||||
- managing allocation
|
||||
- function frame size (important due to Go's use of split stacks),
|
||||
- reflection use (and by-passing reflection for common types)
|
||||
- recursion implications
|
||||
- zero-copy mode (encoding/decoding to byte slice without using temp buffers)
|
||||
- Correct.
|
||||
Care was taken to precisely handle corner cases like:
|
||||
overflows, nil maps and slices, nil value in stream, etc.
|
||||
- Efficient zero-copying into temporary byte buffers
|
||||
when encoding into or decoding from a byte slice.
|
||||
- Standard field renaming via tags
|
||||
- Encoding from any value
|
||||
(struct, slice, map, primitives, pointers, interface{}, etc)
|
||||
- Decoding into pointer to any non-nil typed value
|
||||
(struct, slice, map, int, float32, bool, string, reflect.Value, etc)
|
||||
- Supports extension functions to handle the encode/decode of custom types
|
||||
- Support Go 1.2 encoding.BinaryMarshaler/BinaryUnmarshaler
|
||||
- Schema-less decoding
|
||||
(decode into a pointer to a nil interface{} as opposed to a typed non-nil value).
|
||||
Includes Options to configure what specific map or slice type to use
|
||||
when decoding an encoded list or map into a nil interface{}
|
||||
- Provides a RPC Server and Client Codec for net/rpc communication protocol.
|
||||
- Msgpack Specific:
|
||||
- Provides extension functions to handle spec-defined extensions (binary, timestamp)
|
||||
- Options to resolve ambiguities in handling raw bytes (as string or []byte)
|
||||
during schema-less decoding (decoding into a nil interface{})
|
||||
- RPC Server/Client Codec for msgpack-rpc protocol defined at:
|
||||
https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
|
||||
- Fast Paths for some container types:
|
||||
For some container types, we circumvent reflection and its associated overhead
|
||||
and allocation costs, and encode/decode directly. These types are:
|
||||
[]interface{}
|
||||
[]int
|
||||
[]string
|
||||
map[interface{}]interface{}
|
||||
map[int]interface{}
|
||||
map[string]interface{}
|
||||
|
||||
Extension Support
|
||||
|
||||
Users can register a function to handle the encoding or decoding of
|
||||
their custom types.
|
||||
|
||||
There are no restrictions on what the custom type can be. Some examples:
|
||||
|
||||
type BisSet []int
|
||||
type BitSet64 uint64
|
||||
type UUID string
|
||||
type MyStructWithUnexportedFields struct { a int; b bool; c []int; }
|
||||
type GifImage struct { ... }
|
||||
|
||||
As an illustration, MyStructWithUnexportedFields would normally be
|
||||
encoded as an empty map because it has no exported fields, while UUID
|
||||
would be encoded as a string. However, with extension support, you can
|
||||
encode any of these however you like.
|
||||
|
||||
RPC
|
||||
|
||||
RPC Client and Server Codecs are implemented, so the codecs can be used
|
||||
with the standard net/rpc package.
|
||||
|
||||
Usage
|
||||
|
||||
Typical usage model:
|
||||
|
||||
// create and configure Handle
|
||||
var (
|
||||
bh codec.BincHandle
|
||||
mh codec.MsgpackHandle
|
||||
)
|
||||
|
||||
mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
|
||||
|
||||
// configure extensions
|
||||
// e.g. for msgpack, define functions and enable Time support for tag 1
|
||||
// mh.AddExt(reflect.TypeOf(time.Time{}), 1, myMsgpackTimeEncodeExtFn, myMsgpackTimeDecodeExtFn)
|
||||
|
||||
// create and use decoder/encoder
|
||||
var (
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
b []byte
|
||||
h = &bh // or mh to use msgpack
|
||||
)
|
||||
|
||||
dec = codec.NewDecoder(r, h)
|
||||
dec = codec.NewDecoderBytes(b, h)
|
||||
err = dec.Decode(&v)
|
||||
|
||||
enc = codec.NewEncoder(w, h)
|
||||
enc = codec.NewEncoderBytes(&b, h)
|
||||
err = enc.Encode(v)
|
||||
|
||||
//RPC Server
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
rpcCodec := codec.GoRpc.ServerCodec(conn, h)
|
||||
//OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h)
|
||||
rpc.ServeCodec(rpcCodec)
|
||||
}
|
||||
}()
|
||||
|
||||
//RPC Communication (client side)
|
||||
conn, err = net.Dial("tcp", "localhost:5555")
|
||||
rpcCodec := codec.GoRpc.ClientCodec(conn, h)
|
||||
//OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h)
|
||||
client := rpc.NewClientWithCodec(rpcCodec)
|
||||
|
||||
Representative Benchmark Results
|
||||
|
||||
Run the benchmark suite using:
|
||||
go test -bi -bench=. -benchmem
|
||||
|
||||
To run full benchmark suite (including against vmsgpack and bson),
|
||||
see notes in ext_dep_test.go
|
||||
|
||||
*/
|
||||
package codec
|
||||
174
vendor/github.com/hashicorp/go-msgpack/codec/README.md
generated
vendored
Normal file
174
vendor/github.com/hashicorp/go-msgpack/codec/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# Codec
|
||||
|
||||
High Performance and Feature-Rich Idiomatic Go Library providing
|
||||
encode/decode support for different serialization formats.
|
||||
|
||||
Supported Serialization formats are:
|
||||
|
||||
- msgpack: [https://github.com/msgpack/msgpack]
|
||||
- binc: [http://github.com/ugorji/binc]
|
||||
|
||||
To install:
|
||||
|
||||
go get github.com/ugorji/go/codec
|
||||
|
||||
Online documentation: [http://godoc.org/github.com/ugorji/go/codec]
|
||||
|
||||
The idiomatic Go support is as seen in other encoding packages in
|
||||
the standard library (ie json, xml, gob, etc).
|
||||
|
||||
Rich Feature Set includes:
|
||||
|
||||
- Simple but extremely powerful and feature-rich API
|
||||
- Very High Performance.
|
||||
Our extensive benchmarks show us outperforming Gob, Json and Bson by 2-4X.
|
||||
This was achieved by taking extreme care on:
|
||||
- managing allocation
|
||||
- function frame size (important due to Go's use of split stacks),
|
||||
- reflection use (and by-passing reflection for common types)
|
||||
- recursion implications
|
||||
- zero-copy mode (encoding/decoding to byte slice without using temp buffers)
|
||||
- Correct.
|
||||
Care was taken to precisely handle corner cases like:
|
||||
overflows, nil maps and slices, nil value in stream, etc.
|
||||
- Efficient zero-copying into temporary byte buffers
|
||||
when encoding into or decoding from a byte slice.
|
||||
- Standard field renaming via tags
|
||||
- Encoding from any value
|
||||
(struct, slice, map, primitives, pointers, interface{}, etc)
|
||||
- Decoding into pointer to any non-nil typed value
|
||||
(struct, slice, map, int, float32, bool, string, reflect.Value, etc)
|
||||
- Supports extension functions to handle the encode/decode of custom types
|
||||
- Support Go 1.2 encoding.BinaryMarshaler/BinaryUnmarshaler
|
||||
- Schema-less decoding
|
||||
(decode into a pointer to a nil interface{} as opposed to a typed non-nil value).
|
||||
Includes Options to configure what specific map or slice type to use
|
||||
when decoding an encoded list or map into a nil interface{}
|
||||
- Provides a RPC Server and Client Codec for net/rpc communication protocol.
|
||||
- Msgpack Specific:
|
||||
- Provides extension functions to handle spec-defined extensions (binary, timestamp)
|
||||
- Options to resolve ambiguities in handling raw bytes (as string or []byte)
|
||||
during schema-less decoding (decoding into a nil interface{})
|
||||
- RPC Server/Client Codec for msgpack-rpc protocol defined at:
|
||||
https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
|
||||
- Fast Paths for some container types:
|
||||
For some container types, we circumvent reflection and its associated overhead
|
||||
and allocation costs, and encode/decode directly. These types are:
|
||||
[]interface{}
|
||||
[]int
|
||||
[]string
|
||||
map[interface{}]interface{}
|
||||
map[int]interface{}
|
||||
map[string]interface{}
|
||||
|
||||
## Extension Support
|
||||
|
||||
Users can register a function to handle the encoding or decoding of
|
||||
their custom types.
|
||||
|
||||
There are no restrictions on what the custom type can be. Some examples:
|
||||
|
||||
type BisSet []int
|
||||
type BitSet64 uint64
|
||||
type UUID string
|
||||
type MyStructWithUnexportedFields struct { a int; b bool; c []int; }
|
||||
type GifImage struct { ... }
|
||||
|
||||
As an illustration, MyStructWithUnexportedFields would normally be
|
||||
encoded as an empty map because it has no exported fields, while UUID
|
||||
would be encoded as a string. However, with extension support, you can
|
||||
encode any of these however you like.
|
||||
|
||||
## RPC
|
||||
|
||||
RPC Client and Server Codecs are implemented, so the codecs can be used
|
||||
with the standard net/rpc package.
|
||||
|
||||
## Usage
|
||||
|
||||
Typical usage model:
|
||||
|
||||
// create and configure Handle
|
||||
var (
|
||||
bh codec.BincHandle
|
||||
mh codec.MsgpackHandle
|
||||
)
|
||||
|
||||
mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
|
||||
|
||||
// configure extensions
|
||||
// e.g. for msgpack, define functions and enable Time support for tag 1
|
||||
// mh.AddExt(reflect.TypeOf(time.Time{}), 1, myMsgpackTimeEncodeExtFn, myMsgpackTimeDecodeExtFn)
|
||||
|
||||
// create and use decoder/encoder
|
||||
var (
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
b []byte
|
||||
h = &bh // or mh to use msgpack
|
||||
)
|
||||
|
||||
dec = codec.NewDecoder(r, h)
|
||||
dec = codec.NewDecoderBytes(b, h)
|
||||
err = dec.Decode(&v)
|
||||
|
||||
enc = codec.NewEncoder(w, h)
|
||||
enc = codec.NewEncoderBytes(&b, h)
|
||||
err = enc.Encode(v)
|
||||
|
||||
//RPC Server
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
rpcCodec := codec.GoRpc.ServerCodec(conn, h)
|
||||
//OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h)
|
||||
rpc.ServeCodec(rpcCodec)
|
||||
}
|
||||
}()
|
||||
|
||||
//RPC Communication (client side)
|
||||
conn, err = net.Dial("tcp", "localhost:5555")
|
||||
rpcCodec := codec.GoRpc.ClientCodec(conn, h)
|
||||
//OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h)
|
||||
client := rpc.NewClientWithCodec(rpcCodec)
|
||||
|
||||
## Representative Benchmark Results
|
||||
|
||||
A sample run of benchmark using "go test -bi -bench=. -benchmem":
|
||||
|
||||
/proc/cpuinfo: Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz (HT)
|
||||
|
||||
..............................................
|
||||
BENCHMARK INIT: 2013-10-16 11:02:50.345970786 -0400 EDT
|
||||
To run full benchmark comparing encodings (MsgPack, Binc, JSON, GOB, etc), use: "go test -bench=."
|
||||
Benchmark:
|
||||
Struct recursive Depth: 1
|
||||
ApproxDeepSize Of benchmark Struct: 4694 bytes
|
||||
Benchmark One-Pass Run:
|
||||
v-msgpack: len: 1600 bytes
|
||||
bson: len: 3025 bytes
|
||||
msgpack: len: 1560 bytes
|
||||
binc: len: 1187 bytes
|
||||
gob: len: 1972 bytes
|
||||
json: len: 2538 bytes
|
||||
..............................................
|
||||
PASS
|
||||
Benchmark__Msgpack____Encode 50000 54359 ns/op 14953 B/op 83 allocs/op
|
||||
Benchmark__Msgpack____Decode 10000 106531 ns/op 14990 B/op 410 allocs/op
|
||||
Benchmark__Binc_NoSym_Encode 50000 53956 ns/op 14966 B/op 83 allocs/op
|
||||
Benchmark__Binc_NoSym_Decode 10000 103751 ns/op 14529 B/op 386 allocs/op
|
||||
Benchmark__Binc_Sym___Encode 50000 65961 ns/op 17130 B/op 88 allocs/op
|
||||
Benchmark__Binc_Sym___Decode 10000 106310 ns/op 15857 B/op 287 allocs/op
|
||||
Benchmark__Gob________Encode 10000 135944 ns/op 21189 B/op 237 allocs/op
|
||||
Benchmark__Gob________Decode 5000 405390 ns/op 83460 B/op 1841 allocs/op
|
||||
Benchmark__Json_______Encode 20000 79412 ns/op 13874 B/op 102 allocs/op
|
||||
Benchmark__Json_______Decode 10000 247979 ns/op 14202 B/op 493 allocs/op
|
||||
Benchmark__Bson_______Encode 10000 121762 ns/op 27814 B/op 514 allocs/op
|
||||
Benchmark__Bson_______Decode 10000 162126 ns/op 16514 B/op 789 allocs/op
|
||||
Benchmark__VMsgpack___Encode 50000 69155 ns/op 12370 B/op 344 allocs/op
|
||||
Benchmark__VMsgpack___Decode 10000 151609 ns/op 20307 B/op 571 allocs/op
|
||||
ok ugorji.net/codec 30.827s
|
||||
|
||||
To run full benchmark suite (including against vmsgpack and bson),
|
||||
see notes in ext\_dep\_test.go
|
||||
|
||||
786
vendor/github.com/hashicorp/go-msgpack/codec/binc.go
generated
vendored
Normal file
786
vendor/github.com/hashicorp/go-msgpack/codec/binc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,786 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"math"
|
||||
// "reflect"
|
||||
// "sync/atomic"
|
||||
"time"
|
||||
//"fmt"
|
||||
)
|
||||
|
||||
const bincDoPrune = true // No longer needed. Needed before as C lib did not support pruning.
|
||||
|
||||
//var _ = fmt.Printf
|
||||
|
||||
// vd as low 4 bits (there are 16 slots)
|
||||
const (
|
||||
bincVdSpecial byte = iota
|
||||
bincVdPosInt
|
||||
bincVdNegInt
|
||||
bincVdFloat
|
||||
|
||||
bincVdString
|
||||
bincVdByteArray
|
||||
bincVdArray
|
||||
bincVdMap
|
||||
|
||||
bincVdTimestamp
|
||||
bincVdSmallInt
|
||||
bincVdUnicodeOther
|
||||
bincVdSymbol
|
||||
|
||||
bincVdDecimal
|
||||
_ // open slot
|
||||
_ // open slot
|
||||
bincVdCustomExt = 0x0f
|
||||
)
|
||||
|
||||
const (
|
||||
bincSpNil byte = iota
|
||||
bincSpFalse
|
||||
bincSpTrue
|
||||
bincSpNan
|
||||
bincSpPosInf
|
||||
bincSpNegInf
|
||||
bincSpZeroFloat
|
||||
bincSpZero
|
||||
bincSpNegOne
|
||||
)
|
||||
|
||||
const (
|
||||
bincFlBin16 byte = iota
|
||||
bincFlBin32
|
||||
_ // bincFlBin32e
|
||||
bincFlBin64
|
||||
_ // bincFlBin64e
|
||||
// others not currently supported
|
||||
)
|
||||
|
||||
type bincEncDriver struct {
|
||||
w encWriter
|
||||
m map[string]uint16 // symbols
|
||||
s uint32 // symbols sequencer
|
||||
b [8]byte
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) isBuiltinType(rt uintptr) bool {
|
||||
return rt == timeTypId
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeBuiltin(rt uintptr, v interface{}) {
|
||||
switch rt {
|
||||
case timeTypId:
|
||||
bs := encodeTime(v.(time.Time))
|
||||
e.w.writen1(bincVdTimestamp<<4 | uint8(len(bs)))
|
||||
e.w.writeb(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeNil() {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpNil)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpTrue)
|
||||
} else {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeFloat32(f float32) {
|
||||
if f == 0 {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat)
|
||||
return
|
||||
}
|
||||
e.w.writen1(bincVdFloat<<4 | bincFlBin32)
|
||||
e.w.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeFloat64(f float64) {
|
||||
if f == 0 {
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpZeroFloat)
|
||||
return
|
||||
}
|
||||
bigen.PutUint64(e.b[:], math.Float64bits(f))
|
||||
if bincDoPrune {
|
||||
i := 7
|
||||
for ; i >= 0 && (e.b[i] == 0); i-- {
|
||||
}
|
||||
i++
|
||||
if i <= 6 {
|
||||
e.w.writen1(bincVdFloat<<4 | 0x8 | bincFlBin64)
|
||||
e.w.writen1(byte(i))
|
||||
e.w.writeb(e.b[:i])
|
||||
return
|
||||
}
|
||||
}
|
||||
e.w.writen1(bincVdFloat<<4 | bincFlBin64)
|
||||
e.w.writeb(e.b[:])
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encIntegerPrune(bd byte, pos bool, v uint64, lim uint8) {
|
||||
if lim == 4 {
|
||||
bigen.PutUint32(e.b[:lim], uint32(v))
|
||||
} else {
|
||||
bigen.PutUint64(e.b[:lim], v)
|
||||
}
|
||||
if bincDoPrune {
|
||||
i := pruneSignExt(e.b[:lim], pos)
|
||||
e.w.writen1(bd | lim - 1 - byte(i))
|
||||
e.w.writeb(e.b[i:lim])
|
||||
} else {
|
||||
e.w.writen1(bd | lim - 1)
|
||||
e.w.writeb(e.b[:lim])
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeInt(v int64) {
|
||||
const nbd byte = bincVdNegInt << 4
|
||||
switch {
|
||||
case v >= 0:
|
||||
e.encUint(bincVdPosInt<<4, true, uint64(v))
|
||||
case v == -1:
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpNegOne)
|
||||
default:
|
||||
e.encUint(bincVdNegInt<<4, false, uint64(-v))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeUint(v uint64) {
|
||||
e.encUint(bincVdPosInt<<4, true, v)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encUint(bd byte, pos bool, v uint64) {
|
||||
switch {
|
||||
case v == 0:
|
||||
e.w.writen1(bincVdSpecial<<4 | bincSpZero)
|
||||
case pos && v >= 1 && v <= 16:
|
||||
e.w.writen1(bincVdSmallInt<<4 | byte(v-1))
|
||||
case v <= math.MaxUint8:
|
||||
e.w.writen2(bd|0x0, byte(v))
|
||||
case v <= math.MaxUint16:
|
||||
e.w.writen1(bd | 0x01)
|
||||
e.w.writeUint16(uint16(v))
|
||||
case v <= math.MaxUint32:
|
||||
e.encIntegerPrune(bd, pos, v, 4)
|
||||
default:
|
||||
e.encIntegerPrune(bd, pos, v, 8)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeExtPreamble(xtag byte, length int) {
|
||||
e.encLen(bincVdCustomExt<<4, uint64(length))
|
||||
e.w.writen1(xtag)
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeArrayPreamble(length int) {
|
||||
e.encLen(bincVdArray<<4, uint64(length))
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeMapPreamble(length int) {
|
||||
e.encLen(bincVdMap<<4, uint64(length))
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeString(c charEncoding, v string) {
|
||||
l := uint64(len(v))
|
||||
e.encBytesLen(c, l)
|
||||
if l > 0 {
|
||||
e.w.writestr(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeSymbol(v string) {
|
||||
// if WriteSymbolsNoRefs {
|
||||
// e.encodeString(c_UTF8, v)
|
||||
// return
|
||||
// }
|
||||
|
||||
//symbols only offer benefit when string length > 1.
|
||||
//This is because strings with length 1 take only 2 bytes to store
|
||||
//(bd with embedded length, and single byte for string val).
|
||||
|
||||
l := len(v)
|
||||
switch l {
|
||||
case 0:
|
||||
e.encBytesLen(c_UTF8, 0)
|
||||
return
|
||||
case 1:
|
||||
e.encBytesLen(c_UTF8, 1)
|
||||
e.w.writen1(v[0])
|
||||
return
|
||||
}
|
||||
if e.m == nil {
|
||||
e.m = make(map[string]uint16, 16)
|
||||
}
|
||||
ui, ok := e.m[v]
|
||||
if ok {
|
||||
if ui <= math.MaxUint8 {
|
||||
e.w.writen2(bincVdSymbol<<4, byte(ui))
|
||||
} else {
|
||||
e.w.writen1(bincVdSymbol<<4 | 0x8)
|
||||
e.w.writeUint16(ui)
|
||||
}
|
||||
} else {
|
||||
e.s++
|
||||
ui = uint16(e.s)
|
||||
//ui = uint16(atomic.AddUint32(&e.s, 1))
|
||||
e.m[v] = ui
|
||||
var lenprec uint8
|
||||
switch {
|
||||
case l <= math.MaxUint8:
|
||||
// lenprec = 0
|
||||
case l <= math.MaxUint16:
|
||||
lenprec = 1
|
||||
case int64(l) <= math.MaxUint32:
|
||||
lenprec = 2
|
||||
default:
|
||||
lenprec = 3
|
||||
}
|
||||
if ui <= math.MaxUint8 {
|
||||
e.w.writen2(bincVdSymbol<<4|0x0|0x4|lenprec, byte(ui))
|
||||
} else {
|
||||
e.w.writen1(bincVdSymbol<<4 | 0x8 | 0x4 | lenprec)
|
||||
e.w.writeUint16(ui)
|
||||
}
|
||||
switch lenprec {
|
||||
case 0:
|
||||
e.w.writen1(byte(l))
|
||||
case 1:
|
||||
e.w.writeUint16(uint16(l))
|
||||
case 2:
|
||||
e.w.writeUint32(uint32(l))
|
||||
default:
|
||||
e.w.writeUint64(uint64(l))
|
||||
}
|
||||
e.w.writestr(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encodeStringBytes(c charEncoding, v []byte) {
|
||||
l := uint64(len(v))
|
||||
e.encBytesLen(c, l)
|
||||
if l > 0 {
|
||||
e.w.writeb(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encBytesLen(c charEncoding, length uint64) {
|
||||
//TODO: support bincUnicodeOther (for now, just use string or bytearray)
|
||||
if c == c_RAW {
|
||||
e.encLen(bincVdByteArray<<4, length)
|
||||
} else {
|
||||
e.encLen(bincVdString<<4, length)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encLen(bd byte, l uint64) {
|
||||
if l < 12 {
|
||||
e.w.writen1(bd | uint8(l+4))
|
||||
} else {
|
||||
e.encLenNumber(bd, l)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *bincEncDriver) encLenNumber(bd byte, v uint64) {
|
||||
switch {
|
||||
case v <= math.MaxUint8:
|
||||
e.w.writen2(bd, byte(v))
|
||||
case v <= math.MaxUint16:
|
||||
e.w.writen1(bd | 0x01)
|
||||
e.w.writeUint16(uint16(v))
|
||||
case v <= math.MaxUint32:
|
||||
e.w.writen1(bd | 0x02)
|
||||
e.w.writeUint32(uint32(v))
|
||||
default:
|
||||
e.w.writen1(bd | 0x03)
|
||||
e.w.writeUint64(uint64(v))
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
type bincDecDriver struct {
|
||||
r decReader
|
||||
bdRead bool
|
||||
bdType valueType
|
||||
bd byte
|
||||
vd byte
|
||||
vs byte
|
||||
b [8]byte
|
||||
m map[uint32]string // symbols (use uint32 as key, as map optimizes for it)
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) initReadNext() {
|
||||
if d.bdRead {
|
||||
return
|
||||
}
|
||||
d.bd = d.r.readn1()
|
||||
d.vd = d.bd >> 4
|
||||
d.vs = d.bd & 0x0f
|
||||
d.bdRead = true
|
||||
d.bdType = valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) currentEncodedType() valueType {
|
||||
if d.bdType == valueTypeUnset {
|
||||
switch d.vd {
|
||||
case bincVdSpecial:
|
||||
switch d.vs {
|
||||
case bincSpNil:
|
||||
d.bdType = valueTypeNil
|
||||
case bincSpFalse, bincSpTrue:
|
||||
d.bdType = valueTypeBool
|
||||
case bincSpNan, bincSpNegInf, bincSpPosInf, bincSpZeroFloat:
|
||||
d.bdType = valueTypeFloat
|
||||
case bincSpZero:
|
||||
d.bdType = valueTypeUint
|
||||
case bincSpNegOne:
|
||||
d.bdType = valueTypeInt
|
||||
default:
|
||||
decErr("currentEncodedType: Unrecognized special value 0x%x", d.vs)
|
||||
}
|
||||
case bincVdSmallInt:
|
||||
d.bdType = valueTypeUint
|
||||
case bincVdPosInt:
|
||||
d.bdType = valueTypeUint
|
||||
case bincVdNegInt:
|
||||
d.bdType = valueTypeInt
|
||||
case bincVdFloat:
|
||||
d.bdType = valueTypeFloat
|
||||
case bincVdString:
|
||||
d.bdType = valueTypeString
|
||||
case bincVdSymbol:
|
||||
d.bdType = valueTypeSymbol
|
||||
case bincVdByteArray:
|
||||
d.bdType = valueTypeBytes
|
||||
case bincVdTimestamp:
|
||||
d.bdType = valueTypeTimestamp
|
||||
case bincVdCustomExt:
|
||||
d.bdType = valueTypeExt
|
||||
case bincVdArray:
|
||||
d.bdType = valueTypeArray
|
||||
case bincVdMap:
|
||||
d.bdType = valueTypeMap
|
||||
default:
|
||||
decErr("currentEncodedType: Unrecognized d.vd: 0x%x", d.vd)
|
||||
}
|
||||
}
|
||||
return d.bdType
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) tryDecodeAsNil() bool {
|
||||
if d.bd == bincVdSpecial<<4|bincSpNil {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) isBuiltinType(rt uintptr) bool {
|
||||
return rt == timeTypId
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeBuiltin(rt uintptr, v interface{}) {
|
||||
switch rt {
|
||||
case timeTypId:
|
||||
if d.vd != bincVdTimestamp {
|
||||
decErr("Invalid d.vd. Expecting 0x%x. Received: 0x%x", bincVdTimestamp, d.vd)
|
||||
}
|
||||
tt, err := decodeTime(d.r.readn(int(d.vs)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var vt *time.Time = v.(*time.Time)
|
||||
*vt = tt
|
||||
d.bdRead = false
|
||||
}
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decFloatPre(vs, defaultLen byte) {
|
||||
if vs&0x8 == 0 {
|
||||
d.r.readb(d.b[0:defaultLen])
|
||||
} else {
|
||||
l := d.r.readn1()
|
||||
if l > 8 {
|
||||
decErr("At most 8 bytes used to represent float. Received: %v bytes", l)
|
||||
}
|
||||
for i := l; i < 8; i++ {
|
||||
d.b[i] = 0
|
||||
}
|
||||
d.r.readb(d.b[0:l])
|
||||
}
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decFloat() (f float64) {
|
||||
//if true { f = math.Float64frombits(d.r.readUint64()); break; }
|
||||
switch vs := d.vs; vs & 0x7 {
|
||||
case bincFlBin32:
|
||||
d.decFloatPre(vs, 4)
|
||||
f = float64(math.Float32frombits(bigen.Uint32(d.b[0:4])))
|
||||
case bincFlBin64:
|
||||
d.decFloatPre(vs, 8)
|
||||
f = math.Float64frombits(bigen.Uint64(d.b[0:8]))
|
||||
default:
|
||||
decErr("only float32 and float64 are supported. d.vd: 0x%x, d.vs: 0x%x", d.vd, d.vs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decUint() (v uint64) {
|
||||
// need to inline the code (interface conversion and type assertion expensive)
|
||||
switch d.vs {
|
||||
case 0:
|
||||
v = uint64(d.r.readn1())
|
||||
case 1:
|
||||
d.r.readb(d.b[6:])
|
||||
v = uint64(bigen.Uint16(d.b[6:]))
|
||||
case 2:
|
||||
d.b[4] = 0
|
||||
d.r.readb(d.b[5:])
|
||||
v = uint64(bigen.Uint32(d.b[4:]))
|
||||
case 3:
|
||||
d.r.readb(d.b[4:])
|
||||
v = uint64(bigen.Uint32(d.b[4:]))
|
||||
case 4, 5, 6:
|
||||
lim := int(7 - d.vs)
|
||||
d.r.readb(d.b[lim:])
|
||||
for i := 0; i < lim; i++ {
|
||||
d.b[i] = 0
|
||||
}
|
||||
v = uint64(bigen.Uint64(d.b[:]))
|
||||
case 7:
|
||||
d.r.readb(d.b[:])
|
||||
v = uint64(bigen.Uint64(d.b[:]))
|
||||
default:
|
||||
decErr("unsigned integers with greater than 64 bits of precision not supported")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decIntAny() (ui uint64, i int64, neg bool) {
|
||||
switch d.vd {
|
||||
case bincVdPosInt:
|
||||
ui = d.decUint()
|
||||
i = int64(ui)
|
||||
case bincVdNegInt:
|
||||
ui = d.decUint()
|
||||
i = -(int64(ui))
|
||||
neg = true
|
||||
case bincVdSmallInt:
|
||||
i = int64(d.vs) + 1
|
||||
ui = uint64(d.vs) + 1
|
||||
case bincVdSpecial:
|
||||
switch d.vs {
|
||||
case bincSpZero:
|
||||
//i = 0
|
||||
case bincSpNegOne:
|
||||
neg = true
|
||||
ui = 1
|
||||
i = -1
|
||||
default:
|
||||
decErr("numeric decode fails for special value: d.vs: 0x%x", d.vs)
|
||||
}
|
||||
default:
|
||||
decErr("number can only be decoded from uint or int values. d.bd: 0x%x, d.vd: 0x%x", d.bd, d.vd)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeInt(bitsize uint8) (i int64) {
|
||||
_, i, _ = d.decIntAny()
|
||||
checkOverflow(0, i, bitsize)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeUint(bitsize uint8) (ui uint64) {
|
||||
ui, i, neg := d.decIntAny()
|
||||
if neg {
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", i)
|
||||
}
|
||||
checkOverflow(ui, 0, bitsize)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeFloat(chkOverflow32 bool) (f float64) {
|
||||
switch d.vd {
|
||||
case bincVdSpecial:
|
||||
d.bdRead = false
|
||||
switch d.vs {
|
||||
case bincSpNan:
|
||||
return math.NaN()
|
||||
case bincSpPosInf:
|
||||
return math.Inf(1)
|
||||
case bincSpZeroFloat, bincSpZero:
|
||||
return
|
||||
case bincSpNegInf:
|
||||
return math.Inf(-1)
|
||||
default:
|
||||
decErr("Invalid d.vs decoding float where d.vd=bincVdSpecial: %v", d.vs)
|
||||
}
|
||||
case bincVdFloat:
|
||||
f = d.decFloat()
|
||||
default:
|
||||
_, i, _ := d.decIntAny()
|
||||
f = float64(i)
|
||||
}
|
||||
checkOverflowFloat32(f, chkOverflow32)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool only (single byte).
|
||||
func (d *bincDecDriver) decodeBool() (b bool) {
|
||||
switch d.bd {
|
||||
case (bincVdSpecial | bincSpFalse):
|
||||
// b = false
|
||||
case (bincVdSpecial | bincSpTrue):
|
||||
b = true
|
||||
default:
|
||||
decErr("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) readMapLen() (length int) {
|
||||
if d.vd != bincVdMap {
|
||||
decErr("Invalid d.vd for map. Expecting 0x%x. Got: 0x%x", bincVdMap, d.vd)
|
||||
}
|
||||
length = d.decLen()
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) readArrayLen() (length int) {
|
||||
if d.vd != bincVdArray {
|
||||
decErr("Invalid d.vd for array. Expecting 0x%x. Got: 0x%x", bincVdArray, d.vd)
|
||||
}
|
||||
length = d.decLen()
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decLen() int {
|
||||
if d.vs <= 3 {
|
||||
return int(d.decUint())
|
||||
}
|
||||
return int(d.vs - 4)
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeString() (s string) {
|
||||
switch d.vd {
|
||||
case bincVdString, bincVdByteArray:
|
||||
if length := d.decLen(); length > 0 {
|
||||
s = string(d.r.readn(length))
|
||||
}
|
||||
case bincVdSymbol:
|
||||
//from vs: extract numSymbolBytes, containsStringVal, strLenPrecision,
|
||||
//extract symbol
|
||||
//if containsStringVal, read it and put in map
|
||||
//else look in map for string value
|
||||
var symbol uint32
|
||||
vs := d.vs
|
||||
//fmt.Printf(">>>> d.vs: 0b%b, & 0x8: %v, & 0x4: %v\n", d.vs, vs & 0x8, vs & 0x4)
|
||||
if vs&0x8 == 0 {
|
||||
symbol = uint32(d.r.readn1())
|
||||
} else {
|
||||
symbol = uint32(d.r.readUint16())
|
||||
}
|
||||
if d.m == nil {
|
||||
d.m = make(map[uint32]string, 16)
|
||||
}
|
||||
|
||||
if vs&0x4 == 0 {
|
||||
s = d.m[symbol]
|
||||
} else {
|
||||
var slen int
|
||||
switch vs & 0x3 {
|
||||
case 0:
|
||||
slen = int(d.r.readn1())
|
||||
case 1:
|
||||
slen = int(d.r.readUint16())
|
||||
case 2:
|
||||
slen = int(d.r.readUint32())
|
||||
case 3:
|
||||
slen = int(d.r.readUint64())
|
||||
}
|
||||
s = string(d.r.readn(slen))
|
||||
d.m[symbol] = s
|
||||
}
|
||||
default:
|
||||
decErr("Invalid d.vd for string. Expecting string:0x%x, bytearray:0x%x or symbol: 0x%x. Got: 0x%x",
|
||||
bincVdString, bincVdByteArray, bincVdSymbol, d.vd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeBytes(bs []byte) (bsOut []byte, changed bool) {
|
||||
var clen int
|
||||
switch d.vd {
|
||||
case bincVdString, bincVdByteArray:
|
||||
clen = d.decLen()
|
||||
default:
|
||||
decErr("Invalid d.vd for bytes. Expecting string:0x%x or bytearray:0x%x. Got: 0x%x",
|
||||
bincVdString, bincVdByteArray, d.vd)
|
||||
}
|
||||
if clen > 0 {
|
||||
// if no contents in stream, don't update the passed byteslice
|
||||
if len(bs) != clen {
|
||||
if len(bs) > clen {
|
||||
bs = bs[:clen]
|
||||
} else {
|
||||
bs = make([]byte, clen)
|
||||
}
|
||||
bsOut = bs
|
||||
changed = true
|
||||
}
|
||||
d.r.readb(bs)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeExt(verifyTag bool, tag byte) (xtag byte, xbs []byte) {
|
||||
switch d.vd {
|
||||
case bincVdCustomExt:
|
||||
l := d.decLen()
|
||||
xtag = d.r.readn1()
|
||||
if verifyTag && xtag != tag {
|
||||
decErr("Wrong extension tag. Got %b. Expecting: %v", xtag, tag)
|
||||
}
|
||||
xbs = d.r.readn(l)
|
||||
case bincVdByteArray:
|
||||
xbs, _ = d.decodeBytes(nil)
|
||||
default:
|
||||
decErr("Invalid d.vd for extensions (Expecting extensions or byte array). Got: 0x%x", d.vd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *bincDecDriver) decodeNaked() (v interface{}, vt valueType, decodeFurther bool) {
|
||||
d.initReadNext()
|
||||
|
||||
switch d.vd {
|
||||
case bincVdSpecial:
|
||||
switch d.vs {
|
||||
case bincSpNil:
|
||||
vt = valueTypeNil
|
||||
case bincSpFalse:
|
||||
vt = valueTypeBool
|
||||
v = false
|
||||
case bincSpTrue:
|
||||
vt = valueTypeBool
|
||||
v = true
|
||||
case bincSpNan:
|
||||
vt = valueTypeFloat
|
||||
v = math.NaN()
|
||||
case bincSpPosInf:
|
||||
vt = valueTypeFloat
|
||||
v = math.Inf(1)
|
||||
case bincSpNegInf:
|
||||
vt = valueTypeFloat
|
||||
v = math.Inf(-1)
|
||||
case bincSpZeroFloat:
|
||||
vt = valueTypeFloat
|
||||
v = float64(0)
|
||||
case bincSpZero:
|
||||
vt = valueTypeUint
|
||||
v = int64(0) // int8(0)
|
||||
case bincSpNegOne:
|
||||
vt = valueTypeInt
|
||||
v = int64(-1) // int8(-1)
|
||||
default:
|
||||
decErr("decodeNaked: Unrecognized special value 0x%x", d.vs)
|
||||
}
|
||||
case bincVdSmallInt:
|
||||
vt = valueTypeUint
|
||||
v = uint64(int8(d.vs)) + 1 // int8(d.vs) + 1
|
||||
case bincVdPosInt:
|
||||
vt = valueTypeUint
|
||||
v = d.decUint()
|
||||
case bincVdNegInt:
|
||||
vt = valueTypeInt
|
||||
v = -(int64(d.decUint()))
|
||||
case bincVdFloat:
|
||||
vt = valueTypeFloat
|
||||
v = d.decFloat()
|
||||
case bincVdSymbol:
|
||||
vt = valueTypeSymbol
|
||||
v = d.decodeString()
|
||||
case bincVdString:
|
||||
vt = valueTypeString
|
||||
v = d.decodeString()
|
||||
case bincVdByteArray:
|
||||
vt = valueTypeBytes
|
||||
v, _ = d.decodeBytes(nil)
|
||||
case bincVdTimestamp:
|
||||
vt = valueTypeTimestamp
|
||||
tt, err := decodeTime(d.r.readn(int(d.vs)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v = tt
|
||||
case bincVdCustomExt:
|
||||
vt = valueTypeExt
|
||||
l := d.decLen()
|
||||
var re RawExt
|
||||
re.Tag = d.r.readn1()
|
||||
re.Data = d.r.readn(l)
|
||||
v = &re
|
||||
vt = valueTypeExt
|
||||
case bincVdArray:
|
||||
vt = valueTypeArray
|
||||
decodeFurther = true
|
||||
case bincVdMap:
|
||||
vt = valueTypeMap
|
||||
decodeFurther = true
|
||||
default:
|
||||
decErr("decodeNaked: Unrecognized d.vd: 0x%x", d.vd)
|
||||
}
|
||||
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
//BincHandle is a Handle for the Binc Schema-Free Encoding Format
|
||||
//defined at https://github.com/ugorji/binc .
|
||||
//
|
||||
//BincHandle currently supports all Binc features with the following EXCEPTIONS:
|
||||
// - only integers up to 64 bits of precision are supported.
|
||||
// big integers are unsupported.
|
||||
// - Only IEEE 754 binary32 and binary64 floats are supported (ie Go float32 and float64 types).
|
||||
// extended precision and decimal IEEE 754 floats are unsupported.
|
||||
// - Only UTF-8 strings supported.
|
||||
// Unicode_Other Binc types (UTF16, UTF32) are currently unsupported.
|
||||
//Note that these EXCEPTIONS are temporary and full support is possible and may happen soon.
|
||||
type BincHandle struct {
|
||||
BasicHandle
|
||||
}
|
||||
|
||||
func (h *BincHandle) newEncDriver(w encWriter) encDriver {
|
||||
return &bincEncDriver{w: w}
|
||||
}
|
||||
|
||||
func (h *BincHandle) newDecDriver(r decReader) decDriver {
|
||||
return &bincDecDriver{r: r}
|
||||
}
|
||||
|
||||
func (_ *BincHandle) writeExt() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *BincHandle) getBasicHandle() *BasicHandle {
|
||||
return &h.BasicHandle
|
||||
}
|
||||
1048
vendor/github.com/hashicorp/go-msgpack/codec/decode.go
generated
vendored
Normal file
1048
vendor/github.com/hashicorp/go-msgpack/codec/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1001
vendor/github.com/hashicorp/go-msgpack/codec/encode.go
generated
vendored
Normal file
1001
vendor/github.com/hashicorp/go-msgpack/codec/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
596
vendor/github.com/hashicorp/go-msgpack/codec/helper.go
generated
vendored
Normal file
596
vendor/github.com/hashicorp/go-msgpack/codec/helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
// Contains code shared by both encode and decode.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
structTagName = "codec"
|
||||
|
||||
// Support
|
||||
// encoding.BinaryMarshaler: MarshalBinary() (data []byte, err error)
|
||||
// encoding.BinaryUnmarshaler: UnmarshalBinary(data []byte) error
|
||||
// This constant flag will enable or disable it.
|
||||
supportBinaryMarshal = true
|
||||
|
||||
// Each Encoder or Decoder uses a cache of functions based on conditionals,
|
||||
// so that the conditionals are not run every time.
|
||||
//
|
||||
// Either a map or a slice is used to keep track of the functions.
|
||||
// The map is more natural, but has a higher cost than a slice/array.
|
||||
// This flag (useMapForCodecCache) controls which is used.
|
||||
useMapForCodecCache = false
|
||||
|
||||
// For some common container types, we can short-circuit an elaborate
|
||||
// reflection dance and call encode/decode directly.
|
||||
// The currently supported types are:
|
||||
// - slices of strings, or id's (int64,uint64) or interfaces.
|
||||
// - maps of str->str, str->intf, id(int64,uint64)->intf, intf->intf
|
||||
shortCircuitReflectToFastPath = true
|
||||
|
||||
// for debugging, set this to false, to catch panic traces.
|
||||
// Note that this will always cause rpc tests to fail, since they need io.EOF sent via panic.
|
||||
recoverPanicToErr = true
|
||||
|
||||
// if checkStructForEmptyValue, check structs fields to see if an empty value.
|
||||
// This could be an expensive call, so possibly disable it.
|
||||
checkStructForEmptyValue = false
|
||||
|
||||
// if derefForIsEmptyValue, deref pointers and interfaces when checking isEmptyValue
|
||||
derefForIsEmptyValue = false
|
||||
)
|
||||
|
||||
type charEncoding uint8
|
||||
|
||||
const (
|
||||
c_RAW charEncoding = iota
|
||||
c_UTF8
|
||||
c_UTF16LE
|
||||
c_UTF16BE
|
||||
c_UTF32LE
|
||||
c_UTF32BE
|
||||
)
|
||||
|
||||
// valueType is the stream type
|
||||
type valueType uint8
|
||||
|
||||
const (
|
||||
valueTypeUnset valueType = iota
|
||||
valueTypeNil
|
||||
valueTypeInt
|
||||
valueTypeUint
|
||||
valueTypeFloat
|
||||
valueTypeBool
|
||||
valueTypeString
|
||||
valueTypeSymbol
|
||||
valueTypeBytes
|
||||
valueTypeMap
|
||||
valueTypeArray
|
||||
valueTypeTimestamp
|
||||
valueTypeExt
|
||||
|
||||
valueTypeInvalid = 0xff
|
||||
)
|
||||
|
||||
var (
|
||||
bigen = binary.BigEndian
|
||||
structInfoFieldName = "_struct"
|
||||
|
||||
cachedTypeInfo = make(map[uintptr]*typeInfo, 4)
|
||||
cachedTypeInfoMutex sync.RWMutex
|
||||
|
||||
intfSliceTyp = reflect.TypeOf([]interface{}(nil))
|
||||
intfTyp = intfSliceTyp.Elem()
|
||||
|
||||
strSliceTyp = reflect.TypeOf([]string(nil))
|
||||
boolSliceTyp = reflect.TypeOf([]bool(nil))
|
||||
uintSliceTyp = reflect.TypeOf([]uint(nil))
|
||||
uint8SliceTyp = reflect.TypeOf([]uint8(nil))
|
||||
uint16SliceTyp = reflect.TypeOf([]uint16(nil))
|
||||
uint32SliceTyp = reflect.TypeOf([]uint32(nil))
|
||||
uint64SliceTyp = reflect.TypeOf([]uint64(nil))
|
||||
intSliceTyp = reflect.TypeOf([]int(nil))
|
||||
int8SliceTyp = reflect.TypeOf([]int8(nil))
|
||||
int16SliceTyp = reflect.TypeOf([]int16(nil))
|
||||
int32SliceTyp = reflect.TypeOf([]int32(nil))
|
||||
int64SliceTyp = reflect.TypeOf([]int64(nil))
|
||||
float32SliceTyp = reflect.TypeOf([]float32(nil))
|
||||
float64SliceTyp = reflect.TypeOf([]float64(nil))
|
||||
|
||||
mapIntfIntfTyp = reflect.TypeOf(map[interface{}]interface{}(nil))
|
||||
mapStrIntfTyp = reflect.TypeOf(map[string]interface{}(nil))
|
||||
mapStrStrTyp = reflect.TypeOf(map[string]string(nil))
|
||||
|
||||
mapIntIntfTyp = reflect.TypeOf(map[int]interface{}(nil))
|
||||
mapInt64IntfTyp = reflect.TypeOf(map[int64]interface{}(nil))
|
||||
mapUintIntfTyp = reflect.TypeOf(map[uint]interface{}(nil))
|
||||
mapUint64IntfTyp = reflect.TypeOf(map[uint64]interface{}(nil))
|
||||
|
||||
stringTyp = reflect.TypeOf("")
|
||||
timeTyp = reflect.TypeOf(time.Time{})
|
||||
rawExtTyp = reflect.TypeOf(RawExt{})
|
||||
|
||||
mapBySliceTyp = reflect.TypeOf((*MapBySlice)(nil)).Elem()
|
||||
binaryMarshalerTyp = reflect.TypeOf((*binaryMarshaler)(nil)).Elem()
|
||||
binaryUnmarshalerTyp = reflect.TypeOf((*binaryUnmarshaler)(nil)).Elem()
|
||||
|
||||
rawExtTypId = reflect.ValueOf(rawExtTyp).Pointer()
|
||||
intfTypId = reflect.ValueOf(intfTyp).Pointer()
|
||||
timeTypId = reflect.ValueOf(timeTyp).Pointer()
|
||||
|
||||
intfSliceTypId = reflect.ValueOf(intfSliceTyp).Pointer()
|
||||
strSliceTypId = reflect.ValueOf(strSliceTyp).Pointer()
|
||||
|
||||
boolSliceTypId = reflect.ValueOf(boolSliceTyp).Pointer()
|
||||
uintSliceTypId = reflect.ValueOf(uintSliceTyp).Pointer()
|
||||
uint8SliceTypId = reflect.ValueOf(uint8SliceTyp).Pointer()
|
||||
uint16SliceTypId = reflect.ValueOf(uint16SliceTyp).Pointer()
|
||||
uint32SliceTypId = reflect.ValueOf(uint32SliceTyp).Pointer()
|
||||
uint64SliceTypId = reflect.ValueOf(uint64SliceTyp).Pointer()
|
||||
intSliceTypId = reflect.ValueOf(intSliceTyp).Pointer()
|
||||
int8SliceTypId = reflect.ValueOf(int8SliceTyp).Pointer()
|
||||
int16SliceTypId = reflect.ValueOf(int16SliceTyp).Pointer()
|
||||
int32SliceTypId = reflect.ValueOf(int32SliceTyp).Pointer()
|
||||
int64SliceTypId = reflect.ValueOf(int64SliceTyp).Pointer()
|
||||
float32SliceTypId = reflect.ValueOf(float32SliceTyp).Pointer()
|
||||
float64SliceTypId = reflect.ValueOf(float64SliceTyp).Pointer()
|
||||
|
||||
mapStrStrTypId = reflect.ValueOf(mapStrStrTyp).Pointer()
|
||||
mapIntfIntfTypId = reflect.ValueOf(mapIntfIntfTyp).Pointer()
|
||||
mapStrIntfTypId = reflect.ValueOf(mapStrIntfTyp).Pointer()
|
||||
mapIntIntfTypId = reflect.ValueOf(mapIntIntfTyp).Pointer()
|
||||
mapInt64IntfTypId = reflect.ValueOf(mapInt64IntfTyp).Pointer()
|
||||
mapUintIntfTypId = reflect.ValueOf(mapUintIntfTyp).Pointer()
|
||||
mapUint64IntfTypId = reflect.ValueOf(mapUint64IntfTyp).Pointer()
|
||||
// Id = reflect.ValueOf().Pointer()
|
||||
// mapBySliceTypId = reflect.ValueOf(mapBySliceTyp).Pointer()
|
||||
|
||||
binaryMarshalerTypId = reflect.ValueOf(binaryMarshalerTyp).Pointer()
|
||||
binaryUnmarshalerTypId = reflect.ValueOf(binaryUnmarshalerTyp).Pointer()
|
||||
|
||||
intBitsize uint8 = uint8(reflect.TypeOf(int(0)).Bits())
|
||||
uintBitsize uint8 = uint8(reflect.TypeOf(uint(0)).Bits())
|
||||
|
||||
bsAll0x00 = []byte{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
bsAll0xff = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
)
|
||||
|
||||
type binaryUnmarshaler interface {
|
||||
UnmarshalBinary(data []byte) error
|
||||
}
|
||||
|
||||
type binaryMarshaler interface {
|
||||
MarshalBinary() (data []byte, err error)
|
||||
}
|
||||
|
||||
// MapBySlice represents a slice which should be encoded as a map in the stream.
|
||||
// The slice contains a sequence of key-value pairs.
|
||||
type MapBySlice interface {
|
||||
MapBySlice()
|
||||
}
|
||||
|
||||
// WARNING: DO NOT USE DIRECTLY. EXPORTED FOR GODOC BENEFIT. WILL BE REMOVED.
|
||||
//
|
||||
// BasicHandle encapsulates the common options and extension functions.
|
||||
type BasicHandle struct {
|
||||
extHandle
|
||||
EncodeOptions
|
||||
DecodeOptions
|
||||
}
|
||||
|
||||
// Handle is the interface for a specific encoding format.
|
||||
//
|
||||
// Typically, a Handle is pre-configured before first time use,
|
||||
// and not modified while in use. Such a pre-configured Handle
|
||||
// is safe for concurrent access.
|
||||
type Handle interface {
|
||||
writeExt() bool
|
||||
getBasicHandle() *BasicHandle
|
||||
newEncDriver(w encWriter) encDriver
|
||||
newDecDriver(r decReader) decDriver
|
||||
}
|
||||
|
||||
// RawExt represents raw unprocessed extension data.
|
||||
type RawExt struct {
|
||||
Tag byte
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type extTypeTagFn struct {
|
||||
rtid uintptr
|
||||
rt reflect.Type
|
||||
tag byte
|
||||
encFn func(reflect.Value) ([]byte, error)
|
||||
decFn func(reflect.Value, []byte) error
|
||||
}
|
||||
|
||||
type extHandle []*extTypeTagFn
|
||||
|
||||
// AddExt registers an encode and decode function for a reflect.Type.
|
||||
// Note that the type must be a named type, and specifically not
|
||||
// a pointer or Interface. An error is returned if that is not honored.
|
||||
//
|
||||
// To Deregister an ext, call AddExt with 0 tag, nil encfn and nil decfn.
|
||||
func (o *extHandle) AddExt(
|
||||
rt reflect.Type,
|
||||
tag byte,
|
||||
encfn func(reflect.Value) ([]byte, error),
|
||||
decfn func(reflect.Value, []byte) error,
|
||||
) (err error) {
|
||||
// o is a pointer, because we may need to initialize it
|
||||
if rt.PkgPath() == "" || rt.Kind() == reflect.Interface {
|
||||
err = fmt.Errorf("codec.Handle.AddExt: Takes named type, especially not a pointer or interface: %T",
|
||||
reflect.Zero(rt).Interface())
|
||||
return
|
||||
}
|
||||
|
||||
// o cannot be nil, since it is always embedded in a Handle.
|
||||
// if nil, let it panic.
|
||||
// if o == nil {
|
||||
// err = errors.New("codec.Handle.AddExt: extHandle cannot be a nil pointer.")
|
||||
// return
|
||||
// }
|
||||
|
||||
rtid := reflect.ValueOf(rt).Pointer()
|
||||
for _, v := range *o {
|
||||
if v.rtid == rtid {
|
||||
v.tag, v.encFn, v.decFn = tag, encfn, decfn
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*o = append(*o, &extTypeTagFn{rtid, rt, tag, encfn, decfn})
|
||||
return
|
||||
}
|
||||
|
||||
func (o extHandle) getExt(rtid uintptr) *extTypeTagFn {
|
||||
for _, v := range o {
|
||||
if v.rtid == rtid {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o extHandle) getExtForTag(tag byte) *extTypeTagFn {
|
||||
for _, v := range o {
|
||||
if v.tag == tag {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o extHandle) getDecodeExtForTag(tag byte) (
|
||||
rv reflect.Value, fn func(reflect.Value, []byte) error) {
|
||||
if x := o.getExtForTag(tag); x != nil {
|
||||
// ext is only registered for base
|
||||
rv = reflect.New(x.rt).Elem()
|
||||
fn = x.decFn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o extHandle) getDecodeExt(rtid uintptr) (tag byte, fn func(reflect.Value, []byte) error) {
|
||||
if x := o.getExt(rtid); x != nil {
|
||||
tag = x.tag
|
||||
fn = x.decFn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o extHandle) getEncodeExt(rtid uintptr) (tag byte, fn func(reflect.Value) ([]byte, error)) {
|
||||
if x := o.getExt(rtid); x != nil {
|
||||
tag = x.tag
|
||||
fn = x.encFn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type structFieldInfo struct {
|
||||
encName string // encode name
|
||||
|
||||
// only one of 'i' or 'is' can be set. If 'i' is -1, then 'is' has been set.
|
||||
|
||||
is []int // (recursive/embedded) field index in struct
|
||||
i int16 // field index in struct
|
||||
omitEmpty bool
|
||||
toArray bool // if field is _struct, is the toArray set?
|
||||
|
||||
// tag string // tag
|
||||
// name string // field name
|
||||
// encNameBs []byte // encoded name as byte stream
|
||||
// ikind int // kind of the field as an int i.e. int(reflect.Kind)
|
||||
}
|
||||
|
||||
func parseStructFieldInfo(fname string, stag string) *structFieldInfo {
|
||||
if fname == "" {
|
||||
panic("parseStructFieldInfo: No Field Name")
|
||||
}
|
||||
si := structFieldInfo{
|
||||
// name: fname,
|
||||
encName: fname,
|
||||
// tag: stag,
|
||||
}
|
||||
|
||||
if stag != "" {
|
||||
for i, s := range strings.Split(stag, ",") {
|
||||
if i == 0 {
|
||||
if s != "" {
|
||||
si.encName = s
|
||||
}
|
||||
} else {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
si.omitEmpty = true
|
||||
case "toarray":
|
||||
si.toArray = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// si.encNameBs = []byte(si.encName)
|
||||
return &si
|
||||
}
|
||||
|
||||
type sfiSortedByEncName []*structFieldInfo
|
||||
|
||||
func (p sfiSortedByEncName) Len() int {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func (p sfiSortedByEncName) Less(i, j int) bool {
|
||||
return p[i].encName < p[j].encName
|
||||
}
|
||||
|
||||
func (p sfiSortedByEncName) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
// typeInfo keeps information about each type referenced in the encode/decode sequence.
|
||||
//
|
||||
// During an encode/decode sequence, we work as below:
|
||||
// - If base is a built in type, en/decode base value
|
||||
// - If base is registered as an extension, en/decode base value
|
||||
// - If type is binary(M/Unm)arshaler, call Binary(M/Unm)arshal method
|
||||
// - Else decode appropriately based on the reflect.Kind
|
||||
type typeInfo struct {
|
||||
sfi []*structFieldInfo // sorted. Used when enc/dec struct to map.
|
||||
sfip []*structFieldInfo // unsorted. Used when enc/dec struct to array.
|
||||
|
||||
rt reflect.Type
|
||||
rtid uintptr
|
||||
|
||||
// baseId gives pointer to the base reflect.Type, after deferencing
|
||||
// the pointers. E.g. base type of ***time.Time is time.Time.
|
||||
base reflect.Type
|
||||
baseId uintptr
|
||||
baseIndir int8 // number of indirections to get to base
|
||||
|
||||
mbs bool // base type (T or *T) is a MapBySlice
|
||||
|
||||
m bool // base type (T or *T) is a binaryMarshaler
|
||||
unm bool // base type (T or *T) is a binaryUnmarshaler
|
||||
mIndir int8 // number of indirections to get to binaryMarshaler type
|
||||
unmIndir int8 // number of indirections to get to binaryUnmarshaler type
|
||||
toArray bool // whether this (struct) type should be encoded as an array
|
||||
}
|
||||
|
||||
func (ti *typeInfo) indexForEncName(name string) int {
|
||||
//tisfi := ti.sfi
|
||||
const binarySearchThreshold = 16
|
||||
if sfilen := len(ti.sfi); sfilen < binarySearchThreshold {
|
||||
// linear search. faster than binary search in my testing up to 16-field structs.
|
||||
for i, si := range ti.sfi {
|
||||
if si.encName == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// binary search. adapted from sort/search.go.
|
||||
h, i, j := 0, 0, sfilen
|
||||
for i < j {
|
||||
h = i + (j-i)/2
|
||||
if ti.sfi[h].encName < name {
|
||||
i = h + 1
|
||||
} else {
|
||||
j = h
|
||||
}
|
||||
}
|
||||
if i < sfilen && ti.sfi[i].encName == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func getTypeInfo(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
|
||||
var ok bool
|
||||
cachedTypeInfoMutex.RLock()
|
||||
pti, ok = cachedTypeInfo[rtid]
|
||||
cachedTypeInfoMutex.RUnlock()
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
cachedTypeInfoMutex.Lock()
|
||||
defer cachedTypeInfoMutex.Unlock()
|
||||
if pti, ok = cachedTypeInfo[rtid]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
ti := typeInfo{rt: rt, rtid: rtid}
|
||||
pti = &ti
|
||||
|
||||
var indir int8
|
||||
if ok, indir = implementsIntf(rt, binaryMarshalerTyp); ok {
|
||||
ti.m, ti.mIndir = true, indir
|
||||
}
|
||||
if ok, indir = implementsIntf(rt, binaryUnmarshalerTyp); ok {
|
||||
ti.unm, ti.unmIndir = true, indir
|
||||
}
|
||||
if ok, _ = implementsIntf(rt, mapBySliceTyp); ok {
|
||||
ti.mbs = true
|
||||
}
|
||||
|
||||
pt := rt
|
||||
var ptIndir int8
|
||||
// for ; pt.Kind() == reflect.Ptr; pt, ptIndir = pt.Elem(), ptIndir+1 { }
|
||||
for pt.Kind() == reflect.Ptr {
|
||||
pt = pt.Elem()
|
||||
ptIndir++
|
||||
}
|
||||
if ptIndir == 0 {
|
||||
ti.base = rt
|
||||
ti.baseId = rtid
|
||||
} else {
|
||||
ti.base = pt
|
||||
ti.baseId = reflect.ValueOf(pt).Pointer()
|
||||
ti.baseIndir = ptIndir
|
||||
}
|
||||
|
||||
if rt.Kind() == reflect.Struct {
|
||||
var siInfo *structFieldInfo
|
||||
if f, ok := rt.FieldByName(structInfoFieldName); ok {
|
||||
siInfo = parseStructFieldInfo(structInfoFieldName, f.Tag.Get(structTagName))
|
||||
ti.toArray = siInfo.toArray
|
||||
}
|
||||
sfip := make([]*structFieldInfo, 0, rt.NumField())
|
||||
rgetTypeInfo(rt, nil, make(map[string]bool), &sfip, siInfo)
|
||||
|
||||
// // try to put all si close together
|
||||
// const tryToPutAllStructFieldInfoTogether = true
|
||||
// if tryToPutAllStructFieldInfoTogether {
|
||||
// sfip2 := make([]structFieldInfo, len(sfip))
|
||||
// for i, si := range sfip {
|
||||
// sfip2[i] = *si
|
||||
// }
|
||||
// for i := range sfip {
|
||||
// sfip[i] = &sfip2[i]
|
||||
// }
|
||||
// }
|
||||
|
||||
ti.sfip = make([]*structFieldInfo, len(sfip))
|
||||
ti.sfi = make([]*structFieldInfo, len(sfip))
|
||||
copy(ti.sfip, sfip)
|
||||
sort.Sort(sfiSortedByEncName(sfip))
|
||||
copy(ti.sfi, sfip)
|
||||
}
|
||||
// sfi = sfip
|
||||
cachedTypeInfo[rtid] = pti
|
||||
return
|
||||
}
|
||||
|
||||
func rgetTypeInfo(rt reflect.Type, indexstack []int, fnameToHastag map[string]bool,
|
||||
sfi *[]*structFieldInfo, siInfo *structFieldInfo,
|
||||
) {
|
||||
// for rt.Kind() == reflect.Ptr {
|
||||
// // indexstack = append(indexstack, 0)
|
||||
// rt = rt.Elem()
|
||||
// }
|
||||
for j := 0; j < rt.NumField(); j++ {
|
||||
f := rt.Field(j)
|
||||
stag := f.Tag.Get(structTagName)
|
||||
if stag == "-" {
|
||||
continue
|
||||
}
|
||||
if r1, _ := utf8.DecodeRuneInString(f.Name); r1 == utf8.RuneError || !unicode.IsUpper(r1) {
|
||||
continue
|
||||
}
|
||||
// if anonymous and there is no struct tag and its a struct (or pointer to struct), inline it.
|
||||
if f.Anonymous && stag == "" {
|
||||
ft := f.Type
|
||||
for ft.Kind() == reflect.Ptr {
|
||||
ft = ft.Elem()
|
||||
}
|
||||
if ft.Kind() == reflect.Struct {
|
||||
indexstack2 := append(append(make([]int, 0, len(indexstack)+4), indexstack...), j)
|
||||
rgetTypeInfo(ft, indexstack2, fnameToHastag, sfi, siInfo)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// do not let fields with same name in embedded structs override field at higher level.
|
||||
// this must be done after anonymous check, to allow anonymous field
|
||||
// still include their child fields
|
||||
if _, ok := fnameToHastag[f.Name]; ok {
|
||||
continue
|
||||
}
|
||||
si := parseStructFieldInfo(f.Name, stag)
|
||||
// si.ikind = int(f.Type.Kind())
|
||||
if len(indexstack) == 0 {
|
||||
si.i = int16(j)
|
||||
} else {
|
||||
si.i = -1
|
||||
si.is = append(append(make([]int, 0, len(indexstack)+4), indexstack...), j)
|
||||
}
|
||||
|
||||
if siInfo != nil {
|
||||
if siInfo.omitEmpty {
|
||||
si.omitEmpty = true
|
||||
}
|
||||
}
|
||||
*sfi = append(*sfi, si)
|
||||
fnameToHastag[f.Name] = stag != ""
|
||||
}
|
||||
}
|
||||
|
||||
func panicToErr(err *error) {
|
||||
if recoverPanicToErr {
|
||||
if x := recover(); x != nil {
|
||||
//debug.PrintStack()
|
||||
panicValToErr(x, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doPanic(tag string, format string, params ...interface{}) {
|
||||
params2 := make([]interface{}, len(params)+1)
|
||||
params2[0] = tag
|
||||
copy(params2[1:], params)
|
||||
panic(fmt.Errorf("%s: "+format, params2...))
|
||||
}
|
||||
|
||||
func checkOverflowFloat32(f float64, doCheck bool) {
|
||||
if !doCheck {
|
||||
return
|
||||
}
|
||||
// check overflow (logic adapted from std pkg reflect/value.go OverflowFloat()
|
||||
f2 := f
|
||||
if f2 < 0 {
|
||||
f2 = -f
|
||||
}
|
||||
if math.MaxFloat32 < f2 && f2 <= math.MaxFloat64 {
|
||||
decErr("Overflow float32 value: %v", f2)
|
||||
}
|
||||
}
|
||||
|
||||
func checkOverflow(ui uint64, i int64, bitsize uint8) {
|
||||
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
|
||||
if bitsize == 0 {
|
||||
return
|
||||
}
|
||||
if i != 0 {
|
||||
if trunc := (i << (64 - bitsize)) >> (64 - bitsize); i != trunc {
|
||||
decErr("Overflow int value: %v", i)
|
||||
}
|
||||
}
|
||||
if ui != 0 {
|
||||
if trunc := (ui << (64 - bitsize)) >> (64 - bitsize); ui != trunc {
|
||||
decErr("Overflow uint value: %v", ui)
|
||||
}
|
||||
}
|
||||
}
|
||||
132
vendor/github.com/hashicorp/go-msgpack/codec/helper_internal.go
generated
vendored
Normal file
132
vendor/github.com/hashicorp/go-msgpack/codec/helper_internal.go
generated
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
// All non-std package dependencies live in this file,
|
||||
// so porting to different environment is easy (just update functions).
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
raisePanicAfterRecover = false
|
||||
debugging = true
|
||||
)
|
||||
|
||||
func panicValToErr(panicVal interface{}, err *error) {
|
||||
switch xerr := panicVal.(type) {
|
||||
case error:
|
||||
*err = xerr
|
||||
case string:
|
||||
*err = errors.New(xerr)
|
||||
default:
|
||||
*err = fmt.Errorf("%v", panicVal)
|
||||
}
|
||||
if raisePanicAfterRecover {
|
||||
panic(panicVal)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if deref {
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return hIsEmptyValue(v.Elem(), deref, checkStruct)
|
||||
} else {
|
||||
return v.IsNil()
|
||||
}
|
||||
case reflect.Struct:
|
||||
if !checkStruct {
|
||||
return false
|
||||
}
|
||||
// return true if all fields are empty. else return false.
|
||||
|
||||
// we cannot use equality check, because some fields may be maps/slices/etc
|
||||
// and consequently the structs are not comparable.
|
||||
// return v.Interface() == reflect.Zero(v.Type()).Interface()
|
||||
for i, n := 0, v.NumField(); i < n; i++ {
|
||||
if !hIsEmptyValue(v.Field(i), deref, checkStruct) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
return hIsEmptyValue(v, derefForIsEmptyValue, checkStructForEmptyValue)
|
||||
}
|
||||
|
||||
func debugf(format string, args ...interface{}) {
|
||||
if debugging {
|
||||
if len(format) == 0 || format[len(format)-1] != '\n' {
|
||||
format = format + "\n"
|
||||
}
|
||||
fmt.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func pruneSignExt(v []byte, pos bool) (n int) {
|
||||
if len(v) < 2 {
|
||||
} else if pos && v[0] == 0 {
|
||||
for ; v[n] == 0 && n+1 < len(v) && (v[n+1]&(1<<7) == 0); n++ {
|
||||
}
|
||||
} else if !pos && v[0] == 0xff {
|
||||
for ; v[n] == 0xff && n+1 < len(v) && (v[n+1]&(1<<7) != 0); n++ {
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func implementsIntf(typ, iTyp reflect.Type) (success bool, indir int8) {
|
||||
if typ == nil {
|
||||
return
|
||||
}
|
||||
rt := typ
|
||||
// The type might be a pointer and we need to keep
|
||||
// dereferencing to the base type until we find an implementation.
|
||||
for {
|
||||
if rt.Implements(iTyp) {
|
||||
return true, indir
|
||||
}
|
||||
if p := rt; p.Kind() == reflect.Ptr {
|
||||
indir++
|
||||
if indir >= math.MaxInt8 { // insane number of indirections
|
||||
return false, 0
|
||||
}
|
||||
rt = p.Elem()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
// No luck yet, but if this is a base type (non-pointer), the pointer might satisfy.
|
||||
if typ.Kind() != reflect.Ptr {
|
||||
// Not a pointer, but does the pointer work?
|
||||
if reflect.PtrTo(typ).Implements(iTyp) {
|
||||
return true, -1
|
||||
}
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
816
vendor/github.com/hashicorp/go-msgpack/codec/msgpack.go
generated
vendored
Normal file
816
vendor/github.com/hashicorp/go-msgpack/codec/msgpack.go
generated
vendored
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
/*
|
||||
MSGPACK
|
||||
|
||||
Msgpack-c implementation powers the c, c++, python, ruby, etc libraries.
|
||||
We need to maintain compatibility with it and how it encodes integer values
|
||||
without caring about the type.
|
||||
|
||||
For compatibility with behaviour of msgpack-c reference implementation:
|
||||
- Go intX (>0) and uintX
|
||||
IS ENCODED AS
|
||||
msgpack +ve fixnum, unsigned
|
||||
- Go intX (<0)
|
||||
IS ENCODED AS
|
||||
msgpack -ve fixnum, signed
|
||||
|
||||
*/
|
||||
package codec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/rpc"
|
||||
)
|
||||
|
||||
const (
|
||||
mpPosFixNumMin byte = 0x00
|
||||
mpPosFixNumMax = 0x7f
|
||||
mpFixMapMin = 0x80
|
||||
mpFixMapMax = 0x8f
|
||||
mpFixArrayMin = 0x90
|
||||
mpFixArrayMax = 0x9f
|
||||
mpFixStrMin = 0xa0
|
||||
mpFixStrMax = 0xbf
|
||||
mpNil = 0xc0
|
||||
_ = 0xc1
|
||||
mpFalse = 0xc2
|
||||
mpTrue = 0xc3
|
||||
mpFloat = 0xca
|
||||
mpDouble = 0xcb
|
||||
mpUint8 = 0xcc
|
||||
mpUint16 = 0xcd
|
||||
mpUint32 = 0xce
|
||||
mpUint64 = 0xcf
|
||||
mpInt8 = 0xd0
|
||||
mpInt16 = 0xd1
|
||||
mpInt32 = 0xd2
|
||||
mpInt64 = 0xd3
|
||||
|
||||
// extensions below
|
||||
mpBin8 = 0xc4
|
||||
mpBin16 = 0xc5
|
||||
mpBin32 = 0xc6
|
||||
mpExt8 = 0xc7
|
||||
mpExt16 = 0xc8
|
||||
mpExt32 = 0xc9
|
||||
mpFixExt1 = 0xd4
|
||||
mpFixExt2 = 0xd5
|
||||
mpFixExt4 = 0xd6
|
||||
mpFixExt8 = 0xd7
|
||||
mpFixExt16 = 0xd8
|
||||
|
||||
mpStr8 = 0xd9 // new
|
||||
mpStr16 = 0xda
|
||||
mpStr32 = 0xdb
|
||||
|
||||
mpArray16 = 0xdc
|
||||
mpArray32 = 0xdd
|
||||
|
||||
mpMap16 = 0xde
|
||||
mpMap32 = 0xdf
|
||||
|
||||
mpNegFixNumMin = 0xe0
|
||||
mpNegFixNumMax = 0xff
|
||||
)
|
||||
|
||||
// MsgpackSpecRpcMultiArgs is a special type which signifies to the MsgpackSpecRpcCodec
|
||||
// that the backend RPC service takes multiple arguments, which have been arranged
|
||||
// in sequence in the slice.
|
||||
//
|
||||
// The Codec then passes it AS-IS to the rpc service (without wrapping it in an
|
||||
// array of 1 element).
|
||||
type MsgpackSpecRpcMultiArgs []interface{}
|
||||
|
||||
// A MsgpackContainer type specifies the different types of msgpackContainers.
|
||||
type msgpackContainerType struct {
|
||||
fixCutoff int
|
||||
bFixMin, b8, b16, b32 byte
|
||||
hasFixMin, has8, has8Always bool
|
||||
}
|
||||
|
||||
var (
|
||||
msgpackContainerStr = msgpackContainerType{32, mpFixStrMin, mpStr8, mpStr16, mpStr32, true, true, false}
|
||||
msgpackContainerBin = msgpackContainerType{0, 0, mpBin8, mpBin16, mpBin32, false, true, true}
|
||||
msgpackContainerList = msgpackContainerType{16, mpFixArrayMin, 0, mpArray16, mpArray32, true, false, false}
|
||||
msgpackContainerMap = msgpackContainerType{16, mpFixMapMin, 0, mpMap16, mpMap32, true, false, false}
|
||||
)
|
||||
|
||||
//---------------------------------------------
|
||||
|
||||
type msgpackEncDriver struct {
|
||||
w encWriter
|
||||
h *MsgpackHandle
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) isBuiltinType(rt uintptr) bool {
|
||||
//no builtin types. All encodings are based on kinds. Types supported as extensions.
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeBuiltin(rt uintptr, v interface{}) {}
|
||||
|
||||
func (e *msgpackEncDriver) encodeNil() {
|
||||
e.w.writen1(mpNil)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeInt(i int64) {
|
||||
|
||||
switch {
|
||||
case i >= 0:
|
||||
e.encodeUint(uint64(i))
|
||||
case i >= -32:
|
||||
e.w.writen1(byte(i))
|
||||
case i >= math.MinInt8:
|
||||
e.w.writen2(mpInt8, byte(i))
|
||||
case i >= math.MinInt16:
|
||||
e.w.writen1(mpInt16)
|
||||
e.w.writeUint16(uint16(i))
|
||||
case i >= math.MinInt32:
|
||||
e.w.writen1(mpInt32)
|
||||
e.w.writeUint32(uint32(i))
|
||||
default:
|
||||
e.w.writen1(mpInt64)
|
||||
e.w.writeUint64(uint64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeUint(i uint64) {
|
||||
switch {
|
||||
case i <= math.MaxInt8:
|
||||
e.w.writen1(byte(i))
|
||||
case i <= math.MaxUint8:
|
||||
e.w.writen2(mpUint8, byte(i))
|
||||
case i <= math.MaxUint16:
|
||||
e.w.writen1(mpUint16)
|
||||
e.w.writeUint16(uint16(i))
|
||||
case i <= math.MaxUint32:
|
||||
e.w.writen1(mpUint32)
|
||||
e.w.writeUint32(uint32(i))
|
||||
default:
|
||||
e.w.writen1(mpUint64)
|
||||
e.w.writeUint64(uint64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(mpTrue)
|
||||
} else {
|
||||
e.w.writen1(mpFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeFloat32(f float32) {
|
||||
e.w.writen1(mpFloat)
|
||||
e.w.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeFloat64(f float64) {
|
||||
e.w.writen1(mpDouble)
|
||||
e.w.writeUint64(math.Float64bits(f))
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeExtPreamble(xtag byte, l int) {
|
||||
switch {
|
||||
case l == 1:
|
||||
e.w.writen2(mpFixExt1, xtag)
|
||||
case l == 2:
|
||||
e.w.writen2(mpFixExt2, xtag)
|
||||
case l == 4:
|
||||
e.w.writen2(mpFixExt4, xtag)
|
||||
case l == 8:
|
||||
e.w.writen2(mpFixExt8, xtag)
|
||||
case l == 16:
|
||||
e.w.writen2(mpFixExt16, xtag)
|
||||
case l < 256:
|
||||
e.w.writen2(mpExt8, byte(l))
|
||||
e.w.writen1(xtag)
|
||||
case l < 65536:
|
||||
e.w.writen1(mpExt16)
|
||||
e.w.writeUint16(uint16(l))
|
||||
e.w.writen1(xtag)
|
||||
default:
|
||||
e.w.writen1(mpExt32)
|
||||
e.w.writeUint32(uint32(l))
|
||||
e.w.writen1(xtag)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeArrayPreamble(length int) {
|
||||
e.writeContainerLen(msgpackContainerList, length)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeMapPreamble(length int) {
|
||||
e.writeContainerLen(msgpackContainerMap, length)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeString(c charEncoding, s string) {
|
||||
if c == c_RAW && e.h.WriteExt {
|
||||
e.writeContainerLen(msgpackContainerBin, len(s))
|
||||
} else {
|
||||
e.writeContainerLen(msgpackContainerStr, len(s))
|
||||
}
|
||||
if len(s) > 0 {
|
||||
e.w.writestr(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeSymbol(v string) {
|
||||
e.encodeString(c_UTF8, v)
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) encodeStringBytes(c charEncoding, bs []byte) {
|
||||
if c == c_RAW && e.h.WriteExt {
|
||||
e.writeContainerLen(msgpackContainerBin, len(bs))
|
||||
} else {
|
||||
e.writeContainerLen(msgpackContainerStr, len(bs))
|
||||
}
|
||||
if len(bs) > 0 {
|
||||
e.w.writeb(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *msgpackEncDriver) writeContainerLen(ct msgpackContainerType, l int) {
|
||||
switch {
|
||||
case ct.hasFixMin && l < ct.fixCutoff:
|
||||
e.w.writen1(ct.bFixMin | byte(l))
|
||||
case ct.has8 && l < 256 && (ct.has8Always || e.h.WriteExt):
|
||||
e.w.writen2(ct.b8, uint8(l))
|
||||
case l < 65536:
|
||||
e.w.writen1(ct.b16)
|
||||
e.w.writeUint16(uint16(l))
|
||||
default:
|
||||
e.w.writen1(ct.b32)
|
||||
e.w.writeUint32(uint32(l))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------
|
||||
|
||||
type msgpackDecDriver struct {
|
||||
r decReader
|
||||
h *MsgpackHandle
|
||||
bd byte
|
||||
bdRead bool
|
||||
bdType valueType
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) isBuiltinType(rt uintptr) bool {
|
||||
//no builtin types. All encodings are based on kinds. Types supported as extensions.
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) decodeBuiltin(rt uintptr, v interface{}) {}
|
||||
|
||||
// Note: This returns either a primitive (int, bool, etc) for non-containers,
|
||||
// or a containerType, or a specific type denoting nil or extension.
|
||||
// It is called when a nil interface{} is passed, leaving it up to the DecDriver
|
||||
// to introspect the stream and decide how best to decode.
|
||||
// It deciphers the value by looking at the stream first.
|
||||
func (d *msgpackDecDriver) decodeNaked() (v interface{}, vt valueType, decodeFurther bool) {
|
||||
d.initReadNext()
|
||||
bd := d.bd
|
||||
|
||||
switch bd {
|
||||
case mpNil:
|
||||
vt = valueTypeNil
|
||||
d.bdRead = false
|
||||
case mpFalse:
|
||||
vt = valueTypeBool
|
||||
v = false
|
||||
case mpTrue:
|
||||
vt = valueTypeBool
|
||||
v = true
|
||||
|
||||
case mpFloat:
|
||||
vt = valueTypeFloat
|
||||
v = float64(math.Float32frombits(d.r.readUint32()))
|
||||
case mpDouble:
|
||||
vt = valueTypeFloat
|
||||
v = math.Float64frombits(d.r.readUint64())
|
||||
|
||||
case mpUint8:
|
||||
vt = valueTypeUint
|
||||
v = uint64(d.r.readn1())
|
||||
case mpUint16:
|
||||
vt = valueTypeUint
|
||||
v = uint64(d.r.readUint16())
|
||||
case mpUint32:
|
||||
vt = valueTypeUint
|
||||
v = uint64(d.r.readUint32())
|
||||
case mpUint64:
|
||||
vt = valueTypeUint
|
||||
v = uint64(d.r.readUint64())
|
||||
|
||||
case mpInt8:
|
||||
vt = valueTypeInt
|
||||
v = int64(int8(d.r.readn1()))
|
||||
case mpInt16:
|
||||
vt = valueTypeInt
|
||||
v = int64(int16(d.r.readUint16()))
|
||||
case mpInt32:
|
||||
vt = valueTypeInt
|
||||
v = int64(int32(d.r.readUint32()))
|
||||
case mpInt64:
|
||||
vt = valueTypeInt
|
||||
v = int64(int64(d.r.readUint64()))
|
||||
|
||||
default:
|
||||
switch {
|
||||
case bd >= mpPosFixNumMin && bd <= mpPosFixNumMax:
|
||||
// positive fixnum (always signed)
|
||||
vt = valueTypeInt
|
||||
v = int64(int8(bd))
|
||||
case bd >= mpNegFixNumMin && bd <= mpNegFixNumMax:
|
||||
// negative fixnum
|
||||
vt = valueTypeInt
|
||||
v = int64(int8(bd))
|
||||
case bd == mpStr8, bd == mpStr16, bd == mpStr32, bd >= mpFixStrMin && bd <= mpFixStrMax:
|
||||
if d.h.RawToString {
|
||||
var rvm string
|
||||
vt = valueTypeString
|
||||
v = &rvm
|
||||
} else {
|
||||
var rvm = []byte{}
|
||||
vt = valueTypeBytes
|
||||
v = &rvm
|
||||
}
|
||||
decodeFurther = true
|
||||
case bd == mpBin8, bd == mpBin16, bd == mpBin32:
|
||||
var rvm = []byte{}
|
||||
vt = valueTypeBytes
|
||||
v = &rvm
|
||||
decodeFurther = true
|
||||
case bd == mpArray16, bd == mpArray32, bd >= mpFixArrayMin && bd <= mpFixArrayMax:
|
||||
vt = valueTypeArray
|
||||
decodeFurther = true
|
||||
case bd == mpMap16, bd == mpMap32, bd >= mpFixMapMin && bd <= mpFixMapMax:
|
||||
vt = valueTypeMap
|
||||
decodeFurther = true
|
||||
case bd >= mpFixExt1 && bd <= mpFixExt16, bd >= mpExt8 && bd <= mpExt32:
|
||||
clen := d.readExtLen()
|
||||
var re RawExt
|
||||
re.Tag = d.r.readn1()
|
||||
re.Data = d.r.readn(clen)
|
||||
v = &re
|
||||
vt = valueTypeExt
|
||||
default:
|
||||
decErr("Nil-Deciphered DecodeValue: %s: hex: %x, dec: %d", msgBadDesc, bd, bd)
|
||||
}
|
||||
}
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// int can be decoded from msgpack type: intXXX or uintXXX
|
||||
func (d *msgpackDecDriver) decodeInt(bitsize uint8) (i int64) {
|
||||
switch d.bd {
|
||||
case mpUint8:
|
||||
i = int64(uint64(d.r.readn1()))
|
||||
case mpUint16:
|
||||
i = int64(uint64(d.r.readUint16()))
|
||||
case mpUint32:
|
||||
i = int64(uint64(d.r.readUint32()))
|
||||
case mpUint64:
|
||||
i = int64(d.r.readUint64())
|
||||
case mpInt8:
|
||||
i = int64(int8(d.r.readn1()))
|
||||
case mpInt16:
|
||||
i = int64(int16(d.r.readUint16()))
|
||||
case mpInt32:
|
||||
i = int64(int32(d.r.readUint32()))
|
||||
case mpInt64:
|
||||
i = int64(d.r.readUint64())
|
||||
default:
|
||||
switch {
|
||||
case d.bd >= mpPosFixNumMin && d.bd <= mpPosFixNumMax:
|
||||
i = int64(int8(d.bd))
|
||||
case d.bd >= mpNegFixNumMin && d.bd <= mpNegFixNumMax:
|
||||
i = int64(int8(d.bd))
|
||||
default:
|
||||
decErr("Unhandled single-byte unsigned integer value: %s: %x", msgBadDesc, d.bd)
|
||||
}
|
||||
}
|
||||
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
|
||||
if bitsize > 0 {
|
||||
if trunc := (i << (64 - bitsize)) >> (64 - bitsize); i != trunc {
|
||||
decErr("Overflow int value: %v", i)
|
||||
}
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// uint can be decoded from msgpack type: intXXX or uintXXX
|
||||
func (d *msgpackDecDriver) decodeUint(bitsize uint8) (ui uint64) {
|
||||
switch d.bd {
|
||||
case mpUint8:
|
||||
ui = uint64(d.r.readn1())
|
||||
case mpUint16:
|
||||
ui = uint64(d.r.readUint16())
|
||||
case mpUint32:
|
||||
ui = uint64(d.r.readUint32())
|
||||
case mpUint64:
|
||||
ui = d.r.readUint64()
|
||||
case mpInt8:
|
||||
if i := int64(int8(d.r.readn1())); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", i)
|
||||
}
|
||||
case mpInt16:
|
||||
if i := int64(int16(d.r.readUint16())); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", i)
|
||||
}
|
||||
case mpInt32:
|
||||
if i := int64(int32(d.r.readUint32())); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", i)
|
||||
}
|
||||
case mpInt64:
|
||||
if i := int64(d.r.readUint64()); i >= 0 {
|
||||
ui = uint64(i)
|
||||
} else {
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", i)
|
||||
}
|
||||
default:
|
||||
switch {
|
||||
case d.bd >= mpPosFixNumMin && d.bd <= mpPosFixNumMax:
|
||||
ui = uint64(d.bd)
|
||||
case d.bd >= mpNegFixNumMin && d.bd <= mpNegFixNumMax:
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", int(d.bd))
|
||||
default:
|
||||
decErr("Unhandled single-byte unsigned integer value: %s: %x", msgBadDesc, d.bd)
|
||||
}
|
||||
}
|
||||
// check overflow (logic adapted from std pkg reflect/value.go OverflowUint()
|
||||
if bitsize > 0 {
|
||||
if trunc := (ui << (64 - bitsize)) >> (64 - bitsize); ui != trunc {
|
||||
decErr("Overflow uint value: %v", ui)
|
||||
}
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// float can either be decoded from msgpack type: float, double or intX
|
||||
func (d *msgpackDecDriver) decodeFloat(chkOverflow32 bool) (f float64) {
|
||||
switch d.bd {
|
||||
case mpFloat:
|
||||
f = float64(math.Float32frombits(d.r.readUint32()))
|
||||
case mpDouble:
|
||||
f = math.Float64frombits(d.r.readUint64())
|
||||
default:
|
||||
f = float64(d.decodeInt(0))
|
||||
}
|
||||
checkOverflowFloat32(f, chkOverflow32)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool, fixnum 0 or 1.
|
||||
func (d *msgpackDecDriver) decodeBool() (b bool) {
|
||||
switch d.bd {
|
||||
case mpFalse, 0:
|
||||
// b = false
|
||||
case mpTrue, 1:
|
||||
b = true
|
||||
default:
|
||||
decErr("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) decodeString() (s string) {
|
||||
clen := d.readContainerLen(msgpackContainerStr)
|
||||
if clen > 0 {
|
||||
s = string(d.r.readn(clen))
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// Callers must check if changed=true (to decide whether to replace the one they have)
|
||||
func (d *msgpackDecDriver) decodeBytes(bs []byte) (bsOut []byte, changed bool) {
|
||||
// bytes can be decoded from msgpackContainerStr or msgpackContainerBin
|
||||
var clen int
|
||||
switch d.bd {
|
||||
case mpBin8, mpBin16, mpBin32:
|
||||
clen = d.readContainerLen(msgpackContainerBin)
|
||||
default:
|
||||
clen = d.readContainerLen(msgpackContainerStr)
|
||||
}
|
||||
// if clen < 0 {
|
||||
// changed = true
|
||||
// panic("length cannot be zero. this cannot be nil.")
|
||||
// }
|
||||
if clen > 0 {
|
||||
// if no contents in stream, don't update the passed byteslice
|
||||
if len(bs) != clen {
|
||||
// Return changed=true if length of passed slice diff from length of bytes in stream
|
||||
if len(bs) > clen {
|
||||
bs = bs[:clen]
|
||||
} else {
|
||||
bs = make([]byte, clen)
|
||||
}
|
||||
bsOut = bs
|
||||
changed = true
|
||||
}
|
||||
d.r.readb(bs)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// Every top-level decode funcs (i.e. decodeValue, decode) must call this first.
|
||||
func (d *msgpackDecDriver) initReadNext() {
|
||||
if d.bdRead {
|
||||
return
|
||||
}
|
||||
d.bd = d.r.readn1()
|
||||
d.bdRead = true
|
||||
d.bdType = valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) currentEncodedType() valueType {
|
||||
if d.bdType == valueTypeUnset {
|
||||
bd := d.bd
|
||||
switch bd {
|
||||
case mpNil:
|
||||
d.bdType = valueTypeNil
|
||||
case mpFalse, mpTrue:
|
||||
d.bdType = valueTypeBool
|
||||
case mpFloat, mpDouble:
|
||||
d.bdType = valueTypeFloat
|
||||
case mpUint8, mpUint16, mpUint32, mpUint64:
|
||||
d.bdType = valueTypeUint
|
||||
case mpInt8, mpInt16, mpInt32, mpInt64:
|
||||
d.bdType = valueTypeInt
|
||||
default:
|
||||
switch {
|
||||
case bd >= mpPosFixNumMin && bd <= mpPosFixNumMax:
|
||||
d.bdType = valueTypeInt
|
||||
case bd >= mpNegFixNumMin && bd <= mpNegFixNumMax:
|
||||
d.bdType = valueTypeInt
|
||||
case bd == mpStr8, bd == mpStr16, bd == mpStr32, bd >= mpFixStrMin && bd <= mpFixStrMax:
|
||||
if d.h.RawToString {
|
||||
d.bdType = valueTypeString
|
||||
} else {
|
||||
d.bdType = valueTypeBytes
|
||||
}
|
||||
case bd == mpBin8, bd == mpBin16, bd == mpBin32:
|
||||
d.bdType = valueTypeBytes
|
||||
case bd == mpArray16, bd == mpArray32, bd >= mpFixArrayMin && bd <= mpFixArrayMax:
|
||||
d.bdType = valueTypeArray
|
||||
case bd == mpMap16, bd == mpMap32, bd >= mpFixMapMin && bd <= mpFixMapMax:
|
||||
d.bdType = valueTypeMap
|
||||
case bd >= mpFixExt1 && bd <= mpFixExt16, bd >= mpExt8 && bd <= mpExt32:
|
||||
d.bdType = valueTypeExt
|
||||
default:
|
||||
decErr("currentEncodedType: Undeciphered descriptor: %s: hex: %x, dec: %d", msgBadDesc, bd, bd)
|
||||
}
|
||||
}
|
||||
}
|
||||
return d.bdType
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) tryDecodeAsNil() bool {
|
||||
if d.bd == mpNil {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readContainerLen(ct msgpackContainerType) (clen int) {
|
||||
bd := d.bd
|
||||
switch {
|
||||
case bd == mpNil:
|
||||
clen = -1 // to represent nil
|
||||
case bd == ct.b8:
|
||||
clen = int(d.r.readn1())
|
||||
case bd == ct.b16:
|
||||
clen = int(d.r.readUint16())
|
||||
case bd == ct.b32:
|
||||
clen = int(d.r.readUint32())
|
||||
case (ct.bFixMin & bd) == ct.bFixMin:
|
||||
clen = int(ct.bFixMin ^ bd)
|
||||
default:
|
||||
decErr("readContainerLen: %s: hex: %x, dec: %d", msgBadDesc, bd, bd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readMapLen() int {
|
||||
return d.readContainerLen(msgpackContainerMap)
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readArrayLen() int {
|
||||
return d.readContainerLen(msgpackContainerList)
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) readExtLen() (clen int) {
|
||||
switch d.bd {
|
||||
case mpNil:
|
||||
clen = -1 // to represent nil
|
||||
case mpFixExt1:
|
||||
clen = 1
|
||||
case mpFixExt2:
|
||||
clen = 2
|
||||
case mpFixExt4:
|
||||
clen = 4
|
||||
case mpFixExt8:
|
||||
clen = 8
|
||||
case mpFixExt16:
|
||||
clen = 16
|
||||
case mpExt8:
|
||||
clen = int(d.r.readn1())
|
||||
case mpExt16:
|
||||
clen = int(d.r.readUint16())
|
||||
case mpExt32:
|
||||
clen = int(d.r.readUint32())
|
||||
default:
|
||||
decErr("decoding ext bytes: found unexpected byte: %x", d.bd)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *msgpackDecDriver) decodeExt(verifyTag bool, tag byte) (xtag byte, xbs []byte) {
|
||||
xbd := d.bd
|
||||
switch {
|
||||
case xbd == mpBin8, xbd == mpBin16, xbd == mpBin32:
|
||||
xbs, _ = d.decodeBytes(nil)
|
||||
case xbd == mpStr8, xbd == mpStr16, xbd == mpStr32,
|
||||
xbd >= mpFixStrMin && xbd <= mpFixStrMax:
|
||||
xbs = []byte(d.decodeString())
|
||||
default:
|
||||
clen := d.readExtLen()
|
||||
xtag = d.r.readn1()
|
||||
if verifyTag && xtag != tag {
|
||||
decErr("Wrong extension tag. Got %b. Expecting: %v", xtag, tag)
|
||||
}
|
||||
xbs = d.r.readn(clen)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
//MsgpackHandle is a Handle for the Msgpack Schema-Free Encoding Format.
|
||||
type MsgpackHandle struct {
|
||||
BasicHandle
|
||||
|
||||
// RawToString controls how raw bytes are decoded into a nil interface{}.
|
||||
RawToString bool
|
||||
// WriteExt flag supports encoding configured extensions with extension tags.
|
||||
// It also controls whether other elements of the new spec are encoded (ie Str8).
|
||||
//
|
||||
// With WriteExt=false, configured extensions are serialized as raw bytes
|
||||
// and Str8 is not encoded.
|
||||
//
|
||||
// A stream can still be decoded into a typed value, provided an appropriate value
|
||||
// is provided, but the type cannot be inferred from the stream. If no appropriate
|
||||
// type is provided (e.g. decoding into a nil interface{}), you get back
|
||||
// a []byte or string based on the setting of RawToString.
|
||||
WriteExt bool
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) newEncDriver(w encWriter) encDriver {
|
||||
return &msgpackEncDriver{w: w, h: h}
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) newDecDriver(r decReader) decDriver {
|
||||
return &msgpackDecDriver{r: r, h: h}
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) writeExt() bool {
|
||||
return h.WriteExt
|
||||
}
|
||||
|
||||
func (h *MsgpackHandle) getBasicHandle() *BasicHandle {
|
||||
return &h.BasicHandle
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
type msgpackSpecRpcCodec struct {
|
||||
rpcCodec
|
||||
}
|
||||
|
||||
// /////////////// Spec RPC Codec ///////////////////
|
||||
func (c *msgpackSpecRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error {
|
||||
// WriteRequest can write to both a Go service, and other services that do
|
||||
// not abide by the 1 argument rule of a Go service.
|
||||
// We discriminate based on if the body is a MsgpackSpecRpcMultiArgs
|
||||
var bodyArr []interface{}
|
||||
if m, ok := body.(MsgpackSpecRpcMultiArgs); ok {
|
||||
bodyArr = ([]interface{})(m)
|
||||
} else {
|
||||
bodyArr = []interface{}{body}
|
||||
}
|
||||
r2 := []interface{}{0, uint32(r.Seq), r.ServiceMethod, bodyArr}
|
||||
return c.write(r2, nil, false, true)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
|
||||
var moe interface{}
|
||||
if r.Error != "" {
|
||||
moe = r.Error
|
||||
}
|
||||
if moe != nil && body != nil {
|
||||
body = nil
|
||||
}
|
||||
r2 := []interface{}{1, uint32(r.Seq), moe, body}
|
||||
return c.write(r2, nil, false, true)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) ReadResponseHeader(r *rpc.Response) error {
|
||||
return c.parseCustomHeader(1, &r.Seq, &r.Error)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) ReadRequestHeader(r *rpc.Request) error {
|
||||
return c.parseCustomHeader(0, &r.Seq, &r.ServiceMethod)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) ReadRequestBody(body interface{}) error {
|
||||
if body == nil { // read and discard
|
||||
return c.read(nil)
|
||||
}
|
||||
bodyArr := []interface{}{body}
|
||||
return c.read(&bodyArr)
|
||||
}
|
||||
|
||||
func (c *msgpackSpecRpcCodec) parseCustomHeader(expectTypeByte byte, msgid *uint64, methodOrError *string) (err error) {
|
||||
|
||||
if c.cls {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// We read the response header by hand
|
||||
// so that the body can be decoded on its own from the stream at a later time.
|
||||
|
||||
const fia byte = 0x94 //four item array descriptor value
|
||||
// Not sure why the panic of EOF is swallowed above.
|
||||
// if bs1 := c.dec.r.readn1(); bs1 != fia {
|
||||
// err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, bs1)
|
||||
// return
|
||||
// }
|
||||
var b byte
|
||||
b, err = c.br.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if b != fia {
|
||||
err = fmt.Errorf("Unexpected value for array descriptor: Expecting %v. Received %v", fia, b)
|
||||
return
|
||||
}
|
||||
|
||||
if err = c.read(&b); err != nil {
|
||||
return
|
||||
}
|
||||
if b != expectTypeByte {
|
||||
err = fmt.Errorf("Unexpected byte descriptor in header. Expecting %v. Received %v", expectTypeByte, b)
|
||||
return
|
||||
}
|
||||
if err = c.read(msgid); err != nil {
|
||||
return
|
||||
}
|
||||
if err = c.read(methodOrError); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
// msgpackSpecRpc is the implementation of Rpc that uses custom communication protocol
|
||||
// as defined in the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
|
||||
type msgpackSpecRpc struct{}
|
||||
|
||||
// MsgpackSpecRpc implements Rpc using the communication protocol defined in
|
||||
// the msgpack spec at https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md .
|
||||
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered.
|
||||
var MsgpackSpecRpc msgpackSpecRpc
|
||||
|
||||
func (x msgpackSpecRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
|
||||
return &msgpackSpecRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
func (x msgpackSpecRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec {
|
||||
return &msgpackSpecRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
var _ decDriver = (*msgpackDecDriver)(nil)
|
||||
var _ encDriver = (*msgpackEncDriver)(nil)
|
||||
110
vendor/github.com/hashicorp/go-msgpack/codec/msgpack_test.py
generated
vendored
Normal file
110
vendor/github.com/hashicorp/go-msgpack/codec/msgpack_test.py
generated
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# This will create golden files in a directory passed to it.
|
||||
# A Test calls this internally to create the golden files
|
||||
# So it can process them (so we don't have to checkin the files).
|
||||
|
||||
import msgpack, msgpackrpc, sys, os, threading
|
||||
|
||||
def get_test_data_list():
|
||||
# get list with all primitive types, and a combo type
|
||||
l0 = [
|
||||
-8,
|
||||
-1616,
|
||||
-32323232,
|
||||
-6464646464646464,
|
||||
192,
|
||||
1616,
|
||||
32323232,
|
||||
6464646464646464,
|
||||
192,
|
||||
-3232.0,
|
||||
-6464646464.0,
|
||||
3232.0,
|
||||
6464646464.0,
|
||||
False,
|
||||
True,
|
||||
None,
|
||||
"someday",
|
||||
"",
|
||||
"bytestring",
|
||||
1328176922000002000,
|
||||
-2206187877999998000,
|
||||
0,
|
||||
-6795364578871345152
|
||||
]
|
||||
l1 = [
|
||||
{ "true": True,
|
||||
"false": False },
|
||||
{ "true": "True",
|
||||
"false": False,
|
||||
"uint16(1616)": 1616 },
|
||||
{ "list": [1616, 32323232, True, -3232.0, {"TRUE":True, "FALSE":False}, [True, False] ],
|
||||
"int32":32323232, "bool": True,
|
||||
"LONG STRING": "123456789012345678901234567890123456789012345678901234567890",
|
||||
"SHORT STRING": "1234567890" },
|
||||
{ True: "true", 8: False, "false": 0 }
|
||||
]
|
||||
|
||||
l = []
|
||||
l.extend(l0)
|
||||
l.append(l0)
|
||||
l.extend(l1)
|
||||
return l
|
||||
|
||||
def build_test_data(destdir):
|
||||
l = get_test_data_list()
|
||||
for i in range(len(l)):
|
||||
packer = msgpack.Packer()
|
||||
serialized = packer.pack(l[i])
|
||||
f = open(os.path.join(destdir, str(i) + '.golden'), 'wb')
|
||||
f.write(serialized)
|
||||
f.close()
|
||||
|
||||
def doRpcServer(port, stopTimeSec):
|
||||
class EchoHandler(object):
|
||||
def Echo123(self, msg1, msg2, msg3):
|
||||
return ("1:%s 2:%s 3:%s" % (msg1, msg2, msg3))
|
||||
def EchoStruct(self, msg):
|
||||
return ("%s" % msg)
|
||||
|
||||
addr = msgpackrpc.Address('localhost', port)
|
||||
server = msgpackrpc.Server(EchoHandler())
|
||||
server.listen(addr)
|
||||
# run thread to stop it after stopTimeSec seconds if > 0
|
||||
if stopTimeSec > 0:
|
||||
def myStopRpcServer():
|
||||
server.stop()
|
||||
t = threading.Timer(stopTimeSec, myStopRpcServer)
|
||||
t.start()
|
||||
server.start()
|
||||
|
||||
def doRpcClientToPythonSvc(port):
|
||||
address = msgpackrpc.Address('localhost', port)
|
||||
client = msgpackrpc.Client(address, unpack_encoding='utf-8')
|
||||
print client.call("Echo123", "A1", "B2", "C3")
|
||||
print client.call("EchoStruct", {"A" :"Aa", "B":"Bb", "C":"Cc"})
|
||||
|
||||
def doRpcClientToGoSvc(port):
|
||||
# print ">>>> port: ", port, " <<<<<"
|
||||
address = msgpackrpc.Address('localhost', port)
|
||||
client = msgpackrpc.Client(address, unpack_encoding='utf-8')
|
||||
print client.call("TestRpcInt.Echo123", ["A1", "B2", "C3"])
|
||||
print client.call("TestRpcInt.EchoStruct", {"A" :"Aa", "B":"Bb", "C":"Cc"})
|
||||
|
||||
def doMain(args):
|
||||
if len(args) == 2 and args[0] == "testdata":
|
||||
build_test_data(args[1])
|
||||
elif len(args) == 3 and args[0] == "rpc-server":
|
||||
doRpcServer(int(args[1]), int(args[2]))
|
||||
elif len(args) == 2 and args[0] == "rpc-client-python-service":
|
||||
doRpcClientToPythonSvc(int(args[1]))
|
||||
elif len(args) == 2 and args[0] == "rpc-client-go-service":
|
||||
doRpcClientToGoSvc(int(args[1]))
|
||||
else:
|
||||
print("Usage: msgpack_test.py " +
|
||||
"[testdata|rpc-server|rpc-client-python-service|rpc-client-go-service] ...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
doMain(sys.argv[1:])
|
||||
|
||||
152
vendor/github.com/hashicorp/go-msgpack/codec/rpc.go
generated
vendored
Normal file
152
vendor/github.com/hashicorp/go-msgpack/codec/rpc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net/rpc"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Rpc provides a rpc Server or Client Codec for rpc communication.
|
||||
type Rpc interface {
|
||||
ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec
|
||||
ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
|
||||
}
|
||||
|
||||
// RpcCodecBuffered allows access to the underlying bufio.Reader/Writer
|
||||
// used by the rpc connection. It accomodates use-cases where the connection
|
||||
// should be used by rpc and non-rpc functions, e.g. streaming a file after
|
||||
// sending an rpc response.
|
||||
type RpcCodecBuffered interface {
|
||||
BufferedReader() *bufio.Reader
|
||||
BufferedWriter() *bufio.Writer
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
// rpcCodec defines the struct members and common methods.
|
||||
type rpcCodec struct {
|
||||
rwc io.ReadWriteCloser
|
||||
dec *Decoder
|
||||
enc *Encoder
|
||||
bw *bufio.Writer
|
||||
br *bufio.Reader
|
||||
mu sync.Mutex
|
||||
cls bool
|
||||
}
|
||||
|
||||
func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec {
|
||||
bw := bufio.NewWriter(conn)
|
||||
br := bufio.NewReader(conn)
|
||||
return rpcCodec{
|
||||
rwc: conn,
|
||||
bw: bw,
|
||||
br: br,
|
||||
enc: NewEncoder(bw, h),
|
||||
dec: NewDecoder(br, h),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rpcCodec) BufferedReader() *bufio.Reader {
|
||||
return c.br
|
||||
}
|
||||
|
||||
func (c *rpcCodec) BufferedWriter() *bufio.Writer {
|
||||
return c.bw
|
||||
}
|
||||
|
||||
func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) {
|
||||
if c.cls {
|
||||
return io.EOF
|
||||
}
|
||||
if err = c.enc.Encode(obj1); err != nil {
|
||||
return
|
||||
}
|
||||
if writeObj2 {
|
||||
if err = c.enc.Encode(obj2); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if doFlush && c.bw != nil {
|
||||
return c.bw.Flush()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *rpcCodec) read(obj interface{}) (err error) {
|
||||
if c.cls {
|
||||
return io.EOF
|
||||
}
|
||||
//If nil is passed in, we should still attempt to read content to nowhere.
|
||||
if obj == nil {
|
||||
var obj2 interface{}
|
||||
return c.dec.Decode(&obj2)
|
||||
}
|
||||
return c.dec.Decode(obj)
|
||||
}
|
||||
|
||||
func (c *rpcCodec) Close() error {
|
||||
if c.cls {
|
||||
return io.EOF
|
||||
}
|
||||
c.cls = true
|
||||
return c.rwc.Close()
|
||||
}
|
||||
|
||||
func (c *rpcCodec) ReadResponseBody(body interface{}) error {
|
||||
return c.read(body)
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
type goRpcCodec struct {
|
||||
rpcCodec
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error {
|
||||
// Must protect for concurrent access as per API
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.write(r, body, true, true)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.write(r, body, true, true)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error {
|
||||
return c.read(r)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) ReadRequestHeader(r *rpc.Request) error {
|
||||
return c.read(r)
|
||||
}
|
||||
|
||||
func (c *goRpcCodec) ReadRequestBody(body interface{}) error {
|
||||
return c.read(body)
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
// goRpc is the implementation of Rpc that uses the communication protocol
|
||||
// as defined in net/rpc package.
|
||||
type goRpc struct{}
|
||||
|
||||
// GoRpc implements Rpc using the communication protocol defined in net/rpc package.
|
||||
// Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered.
|
||||
var GoRpc goRpc
|
||||
|
||||
func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec {
|
||||
return &goRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec {
|
||||
return &goRpcCodec{newRPCCodec(conn, h)}
|
||||
}
|
||||
|
||||
var _ RpcCodecBuffered = (*rpcCodec)(nil) // ensure *rpcCodec implements RpcCodecBuffered
|
||||
461
vendor/github.com/hashicorp/go-msgpack/codec/simple.go
generated
vendored
Normal file
461
vendor/github.com/hashicorp/go-msgpack/codec/simple.go
generated
vendored
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import "math"
|
||||
|
||||
const (
|
||||
_ uint8 = iota
|
||||
simpleVdNil = 1
|
||||
simpleVdFalse = 2
|
||||
simpleVdTrue = 3
|
||||
simpleVdFloat32 = 4
|
||||
simpleVdFloat64 = 5
|
||||
|
||||
// each lasts for 4 (ie n, n+1, n+2, n+3)
|
||||
simpleVdPosInt = 8
|
||||
simpleVdNegInt = 12
|
||||
|
||||
// containers: each lasts for 4 (ie n, n+1, n+2, ... n+7)
|
||||
simpleVdString = 216
|
||||
simpleVdByteArray = 224
|
||||
simpleVdArray = 232
|
||||
simpleVdMap = 240
|
||||
simpleVdExt = 248
|
||||
)
|
||||
|
||||
type simpleEncDriver struct {
|
||||
h *SimpleHandle
|
||||
w encWriter
|
||||
//b [8]byte
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) isBuiltinType(rt uintptr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeBuiltin(rt uintptr, v interface{}) {
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeNil() {
|
||||
e.w.writen1(simpleVdNil)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeBool(b bool) {
|
||||
if b {
|
||||
e.w.writen1(simpleVdTrue)
|
||||
} else {
|
||||
e.w.writen1(simpleVdFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeFloat32(f float32) {
|
||||
e.w.writen1(simpleVdFloat32)
|
||||
e.w.writeUint32(math.Float32bits(f))
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeFloat64(f float64) {
|
||||
e.w.writen1(simpleVdFloat64)
|
||||
e.w.writeUint64(math.Float64bits(f))
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeInt(v int64) {
|
||||
if v < 0 {
|
||||
e.encUint(uint64(-v), simpleVdNegInt)
|
||||
} else {
|
||||
e.encUint(uint64(v), simpleVdPosInt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeUint(v uint64) {
|
||||
e.encUint(v, simpleVdPosInt)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encUint(v uint64, bd uint8) {
|
||||
switch {
|
||||
case v <= math.MaxUint8:
|
||||
e.w.writen2(bd, uint8(v))
|
||||
case v <= math.MaxUint16:
|
||||
e.w.writen1(bd + 1)
|
||||
e.w.writeUint16(uint16(v))
|
||||
case v <= math.MaxUint32:
|
||||
e.w.writen1(bd + 2)
|
||||
e.w.writeUint32(uint32(v))
|
||||
case v <= math.MaxUint64:
|
||||
e.w.writen1(bd + 3)
|
||||
e.w.writeUint64(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encLen(bd byte, length int) {
|
||||
switch {
|
||||
case length == 0:
|
||||
e.w.writen1(bd)
|
||||
case length <= math.MaxUint8:
|
||||
e.w.writen1(bd + 1)
|
||||
e.w.writen1(uint8(length))
|
||||
case length <= math.MaxUint16:
|
||||
e.w.writen1(bd + 2)
|
||||
e.w.writeUint16(uint16(length))
|
||||
case int64(length) <= math.MaxUint32:
|
||||
e.w.writen1(bd + 3)
|
||||
e.w.writeUint32(uint32(length))
|
||||
default:
|
||||
e.w.writen1(bd + 4)
|
||||
e.w.writeUint64(uint64(length))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeExtPreamble(xtag byte, length int) {
|
||||
e.encLen(simpleVdExt, length)
|
||||
e.w.writen1(xtag)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeArrayPreamble(length int) {
|
||||
e.encLen(simpleVdArray, length)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeMapPreamble(length int) {
|
||||
e.encLen(simpleVdMap, length)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeString(c charEncoding, v string) {
|
||||
e.encLen(simpleVdString, len(v))
|
||||
e.w.writestr(v)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeSymbol(v string) {
|
||||
e.encodeString(c_UTF8, v)
|
||||
}
|
||||
|
||||
func (e *simpleEncDriver) encodeStringBytes(c charEncoding, v []byte) {
|
||||
e.encLen(simpleVdByteArray, len(v))
|
||||
e.w.writeb(v)
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
type simpleDecDriver struct {
|
||||
h *SimpleHandle
|
||||
r decReader
|
||||
bdRead bool
|
||||
bdType valueType
|
||||
bd byte
|
||||
//b [8]byte
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) initReadNext() {
|
||||
if d.bdRead {
|
||||
return
|
||||
}
|
||||
d.bd = d.r.readn1()
|
||||
d.bdRead = true
|
||||
d.bdType = valueTypeUnset
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) currentEncodedType() valueType {
|
||||
if d.bdType == valueTypeUnset {
|
||||
switch d.bd {
|
||||
case simpleVdNil:
|
||||
d.bdType = valueTypeNil
|
||||
case simpleVdTrue, simpleVdFalse:
|
||||
d.bdType = valueTypeBool
|
||||
case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3:
|
||||
d.bdType = valueTypeUint
|
||||
case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3:
|
||||
d.bdType = valueTypeInt
|
||||
case simpleVdFloat32, simpleVdFloat64:
|
||||
d.bdType = valueTypeFloat
|
||||
case simpleVdString, simpleVdString + 1, simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
|
||||
d.bdType = valueTypeString
|
||||
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
|
||||
d.bdType = valueTypeBytes
|
||||
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
|
||||
d.bdType = valueTypeExt
|
||||
case simpleVdArray, simpleVdArray + 1, simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4:
|
||||
d.bdType = valueTypeArray
|
||||
case simpleVdMap, simpleVdMap + 1, simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4:
|
||||
d.bdType = valueTypeMap
|
||||
default:
|
||||
decErr("currentEncodedType: Unrecognized d.vd: 0x%x", d.bd)
|
||||
}
|
||||
}
|
||||
return d.bdType
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) tryDecodeAsNil() bool {
|
||||
if d.bd == simpleVdNil {
|
||||
d.bdRead = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) isBuiltinType(rt uintptr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeBuiltin(rt uintptr, v interface{}) {
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decIntAny() (ui uint64, i int64, neg bool) {
|
||||
switch d.bd {
|
||||
case simpleVdPosInt:
|
||||
ui = uint64(d.r.readn1())
|
||||
i = int64(ui)
|
||||
case simpleVdPosInt + 1:
|
||||
ui = uint64(d.r.readUint16())
|
||||
i = int64(ui)
|
||||
case simpleVdPosInt + 2:
|
||||
ui = uint64(d.r.readUint32())
|
||||
i = int64(ui)
|
||||
case simpleVdPosInt + 3:
|
||||
ui = uint64(d.r.readUint64())
|
||||
i = int64(ui)
|
||||
case simpleVdNegInt:
|
||||
ui = uint64(d.r.readn1())
|
||||
i = -(int64(ui))
|
||||
neg = true
|
||||
case simpleVdNegInt + 1:
|
||||
ui = uint64(d.r.readUint16())
|
||||
i = -(int64(ui))
|
||||
neg = true
|
||||
case simpleVdNegInt + 2:
|
||||
ui = uint64(d.r.readUint32())
|
||||
i = -(int64(ui))
|
||||
neg = true
|
||||
case simpleVdNegInt + 3:
|
||||
ui = uint64(d.r.readUint64())
|
||||
i = -(int64(ui))
|
||||
neg = true
|
||||
default:
|
||||
decErr("decIntAny: Integer only valid from pos/neg integer1..8. Invalid descriptor: %v", d.bd)
|
||||
}
|
||||
// don't do this check, because callers may only want the unsigned value.
|
||||
// if ui > math.MaxInt64 {
|
||||
// decErr("decIntAny: Integer out of range for signed int64: %v", ui)
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeInt(bitsize uint8) (i int64) {
|
||||
_, i, _ = d.decIntAny()
|
||||
checkOverflow(0, i, bitsize)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeUint(bitsize uint8) (ui uint64) {
|
||||
ui, i, neg := d.decIntAny()
|
||||
if neg {
|
||||
decErr("Assigning negative signed value: %v, to unsigned type", i)
|
||||
}
|
||||
checkOverflow(ui, 0, bitsize)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeFloat(chkOverflow32 bool) (f float64) {
|
||||
switch d.bd {
|
||||
case simpleVdFloat32:
|
||||
f = float64(math.Float32frombits(d.r.readUint32()))
|
||||
case simpleVdFloat64:
|
||||
f = math.Float64frombits(d.r.readUint64())
|
||||
default:
|
||||
if d.bd >= simpleVdPosInt && d.bd <= simpleVdNegInt+3 {
|
||||
_, i, _ := d.decIntAny()
|
||||
f = float64(i)
|
||||
} else {
|
||||
decErr("Float only valid from float32/64: Invalid descriptor: %v", d.bd)
|
||||
}
|
||||
}
|
||||
checkOverflowFloat32(f, chkOverflow32)
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
// bool can be decoded from bool only (single byte).
|
||||
func (d *simpleDecDriver) decodeBool() (b bool) {
|
||||
switch d.bd {
|
||||
case simpleVdTrue:
|
||||
b = true
|
||||
case simpleVdFalse:
|
||||
default:
|
||||
decErr("Invalid single-byte value for bool: %s: %x", msgBadDesc, d.bd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) readMapLen() (length int) {
|
||||
d.bdRead = false
|
||||
return d.decLen()
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) readArrayLen() (length int) {
|
||||
d.bdRead = false
|
||||
return d.decLen()
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decLen() int {
|
||||
switch d.bd % 8 {
|
||||
case 0:
|
||||
return 0
|
||||
case 1:
|
||||
return int(d.r.readn1())
|
||||
case 2:
|
||||
return int(d.r.readUint16())
|
||||
case 3:
|
||||
ui := uint64(d.r.readUint32())
|
||||
checkOverflow(ui, 0, intBitsize)
|
||||
return int(ui)
|
||||
case 4:
|
||||
ui := d.r.readUint64()
|
||||
checkOverflow(ui, 0, intBitsize)
|
||||
return int(ui)
|
||||
}
|
||||
decErr("decLen: Cannot read length: bd%8 must be in range 0..4. Got: %d", d.bd%8)
|
||||
return -1
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeString() (s string) {
|
||||
s = string(d.r.readn(d.decLen()))
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeBytes(bs []byte) (bsOut []byte, changed bool) {
|
||||
if clen := d.decLen(); clen > 0 {
|
||||
// if no contents in stream, don't update the passed byteslice
|
||||
if len(bs) != clen {
|
||||
if len(bs) > clen {
|
||||
bs = bs[:clen]
|
||||
} else {
|
||||
bs = make([]byte, clen)
|
||||
}
|
||||
bsOut = bs
|
||||
changed = true
|
||||
}
|
||||
d.r.readb(bs)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeExt(verifyTag bool, tag byte) (xtag byte, xbs []byte) {
|
||||
switch d.bd {
|
||||
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
|
||||
l := d.decLen()
|
||||
xtag = d.r.readn1()
|
||||
if verifyTag && xtag != tag {
|
||||
decErr("Wrong extension tag. Got %b. Expecting: %v", xtag, tag)
|
||||
}
|
||||
xbs = d.r.readn(l)
|
||||
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
|
||||
xbs, _ = d.decodeBytes(nil)
|
||||
default:
|
||||
decErr("Invalid d.vd for extensions (Expecting extensions or byte array). Got: 0x%x", d.bd)
|
||||
}
|
||||
d.bdRead = false
|
||||
return
|
||||
}
|
||||
|
||||
func (d *simpleDecDriver) decodeNaked() (v interface{}, vt valueType, decodeFurther bool) {
|
||||
d.initReadNext()
|
||||
|
||||
switch d.bd {
|
||||
case simpleVdNil:
|
||||
vt = valueTypeNil
|
||||
case simpleVdFalse:
|
||||
vt = valueTypeBool
|
||||
v = false
|
||||
case simpleVdTrue:
|
||||
vt = valueTypeBool
|
||||
v = true
|
||||
case simpleVdPosInt, simpleVdPosInt + 1, simpleVdPosInt + 2, simpleVdPosInt + 3:
|
||||
vt = valueTypeUint
|
||||
ui, _, _ := d.decIntAny()
|
||||
v = ui
|
||||
case simpleVdNegInt, simpleVdNegInt + 1, simpleVdNegInt + 2, simpleVdNegInt + 3:
|
||||
vt = valueTypeInt
|
||||
_, i, _ := d.decIntAny()
|
||||
v = i
|
||||
case simpleVdFloat32:
|
||||
vt = valueTypeFloat
|
||||
v = d.decodeFloat(true)
|
||||
case simpleVdFloat64:
|
||||
vt = valueTypeFloat
|
||||
v = d.decodeFloat(false)
|
||||
case simpleVdString, simpleVdString + 1, simpleVdString + 2, simpleVdString + 3, simpleVdString + 4:
|
||||
vt = valueTypeString
|
||||
v = d.decodeString()
|
||||
case simpleVdByteArray, simpleVdByteArray + 1, simpleVdByteArray + 2, simpleVdByteArray + 3, simpleVdByteArray + 4:
|
||||
vt = valueTypeBytes
|
||||
v, _ = d.decodeBytes(nil)
|
||||
case simpleVdExt, simpleVdExt + 1, simpleVdExt + 2, simpleVdExt + 3, simpleVdExt + 4:
|
||||
vt = valueTypeExt
|
||||
l := d.decLen()
|
||||
var re RawExt
|
||||
re.Tag = d.r.readn1()
|
||||
re.Data = d.r.readn(l)
|
||||
v = &re
|
||||
vt = valueTypeExt
|
||||
case simpleVdArray, simpleVdArray + 1, simpleVdArray + 2, simpleVdArray + 3, simpleVdArray + 4:
|
||||
vt = valueTypeArray
|
||||
decodeFurther = true
|
||||
case simpleVdMap, simpleVdMap + 1, simpleVdMap + 2, simpleVdMap + 3, simpleVdMap + 4:
|
||||
vt = valueTypeMap
|
||||
decodeFurther = true
|
||||
default:
|
||||
decErr("decodeNaked: Unrecognized d.vd: 0x%x", d.bd)
|
||||
}
|
||||
|
||||
if !decodeFurther {
|
||||
d.bdRead = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
|
||||
// SimpleHandle is a Handle for a very simple encoding format.
|
||||
//
|
||||
// simple is a simplistic codec similar to binc, but not as compact.
|
||||
// - Encoding of a value is always preceeded by the descriptor byte (bd)
|
||||
// - True, false, nil are encoded fully in 1 byte (the descriptor)
|
||||
// - Integers (intXXX, uintXXX) are encoded in 1, 2, 4 or 8 bytes (plus a descriptor byte).
|
||||
// There are positive (uintXXX and intXXX >= 0) and negative (intXXX < 0) integers.
|
||||
// - Floats are encoded in 4 or 8 bytes (plus a descriptor byte)
|
||||
// - Lenght of containers (strings, bytes, array, map, extensions)
|
||||
// are encoded in 0, 1, 2, 4 or 8 bytes.
|
||||
// Zero-length containers have no length encoded.
|
||||
// For others, the number of bytes is given by pow(2, bd%3)
|
||||
// - maps are encoded as [bd] [length] [[key][value]]...
|
||||
// - arrays are encoded as [bd] [length] [value]...
|
||||
// - extensions are encoded as [bd] [length] [tag] [byte]...
|
||||
// - strings/bytearrays are encoded as [bd] [length] [byte]...
|
||||
//
|
||||
// The full spec will be published soon.
|
||||
type SimpleHandle struct {
|
||||
BasicHandle
|
||||
}
|
||||
|
||||
func (h *SimpleHandle) newEncDriver(w encWriter) encDriver {
|
||||
return &simpleEncDriver{w: w, h: h}
|
||||
}
|
||||
|
||||
func (h *SimpleHandle) newDecDriver(r decReader) decDriver {
|
||||
return &simpleDecDriver{r: r, h: h}
|
||||
}
|
||||
|
||||
func (_ *SimpleHandle) writeExt() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *SimpleHandle) getBasicHandle() *BasicHandle {
|
||||
return &h.BasicHandle
|
||||
}
|
||||
|
||||
var _ decDriver = (*simpleDecDriver)(nil)
|
||||
var _ encDriver = (*simpleEncDriver)(nil)
|
||||
193
vendor/github.com/hashicorp/go-msgpack/codec/time.go
generated
vendored
Normal file
193
vendor/github.com/hashicorp/go-msgpack/codec/time.go
generated
vendored
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license found in the LICENSE file.
|
||||
|
||||
package codec
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
timeDigits = [...]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
||||
)
|
||||
|
||||
// EncodeTime encodes a time.Time as a []byte, including
|
||||
// information on the instant in time and UTC offset.
|
||||
//
|
||||
// Format Description
|
||||
//
|
||||
// A timestamp is composed of 3 components:
|
||||
//
|
||||
// - secs: signed integer representing seconds since unix epoch
|
||||
// - nsces: unsigned integer representing fractional seconds as a
|
||||
// nanosecond offset within secs, in the range 0 <= nsecs < 1e9
|
||||
// - tz: signed integer representing timezone offset in minutes east of UTC,
|
||||
// and a dst (daylight savings time) flag
|
||||
//
|
||||
// When encoding a timestamp, the first byte is the descriptor, which
|
||||
// defines which components are encoded and how many bytes are used to
|
||||
// encode secs and nsecs components. *If secs/nsecs is 0 or tz is UTC, it
|
||||
// is not encoded in the byte array explicitly*.
|
||||
//
|
||||
// Descriptor 8 bits are of the form `A B C DDD EE`:
|
||||
// A: Is secs component encoded? 1 = true
|
||||
// B: Is nsecs component encoded? 1 = true
|
||||
// C: Is tz component encoded? 1 = true
|
||||
// DDD: Number of extra bytes for secs (range 0-7).
|
||||
// If A = 1, secs encoded in DDD+1 bytes.
|
||||
// If A = 0, secs is not encoded, and is assumed to be 0.
|
||||
// If A = 1, then we need at least 1 byte to encode secs.
|
||||
// DDD says the number of extra bytes beyond that 1.
|
||||
// E.g. if DDD=0, then secs is represented in 1 byte.
|
||||
// if DDD=2, then secs is represented in 3 bytes.
|
||||
// EE: Number of extra bytes for nsecs (range 0-3).
|
||||
// If B = 1, nsecs encoded in EE+1 bytes (similar to secs/DDD above)
|
||||
//
|
||||
// Following the descriptor bytes, subsequent bytes are:
|
||||
//
|
||||
// secs component encoded in `DDD + 1` bytes (if A == 1)
|
||||
// nsecs component encoded in `EE + 1` bytes (if B == 1)
|
||||
// tz component encoded in 2 bytes (if C == 1)
|
||||
//
|
||||
// secs and nsecs components are integers encoded in a BigEndian
|
||||
// 2-complement encoding format.
|
||||
//
|
||||
// tz component is encoded as 2 bytes (16 bits). Most significant bit 15 to
|
||||
// Least significant bit 0 are described below:
|
||||
//
|
||||
// Timezone offset has a range of -12:00 to +14:00 (ie -720 to +840 minutes).
|
||||
// Bit 15 = have\_dst: set to 1 if we set the dst flag.
|
||||
// Bit 14 = dst\_on: set to 1 if dst is in effect at the time, or 0 if not.
|
||||
// Bits 13..0 = timezone offset in minutes. It is a signed integer in Big Endian format.
|
||||
//
|
||||
func encodeTime(t time.Time) []byte {
|
||||
//t := rv.Interface().(time.Time)
|
||||
tsecs, tnsecs := t.Unix(), t.Nanosecond()
|
||||
var (
|
||||
bd byte
|
||||
btmp [8]byte
|
||||
bs [16]byte
|
||||
i int = 1
|
||||
)
|
||||
l := t.Location()
|
||||
if l == time.UTC {
|
||||
l = nil
|
||||
}
|
||||
if tsecs != 0 {
|
||||
bd = bd | 0x80
|
||||
bigen.PutUint64(btmp[:], uint64(tsecs))
|
||||
f := pruneSignExt(btmp[:], tsecs >= 0)
|
||||
bd = bd | (byte(7-f) << 2)
|
||||
copy(bs[i:], btmp[f:])
|
||||
i = i + (8 - f)
|
||||
}
|
||||
if tnsecs != 0 {
|
||||
bd = bd | 0x40
|
||||
bigen.PutUint32(btmp[:4], uint32(tnsecs))
|
||||
f := pruneSignExt(btmp[:4], true)
|
||||
bd = bd | byte(3-f)
|
||||
copy(bs[i:], btmp[f:4])
|
||||
i = i + (4 - f)
|
||||
}
|
||||
if l != nil {
|
||||
bd = bd | 0x20
|
||||
// Note that Go Libs do not give access to dst flag.
|
||||
_, zoneOffset := t.Zone()
|
||||
//zoneName, zoneOffset := t.Zone()
|
||||
zoneOffset /= 60
|
||||
z := uint16(zoneOffset)
|
||||
bigen.PutUint16(btmp[:2], z)
|
||||
// clear dst flags
|
||||
bs[i] = btmp[0] & 0x3f
|
||||
bs[i+1] = btmp[1]
|
||||
i = i + 2
|
||||
}
|
||||
bs[0] = bd
|
||||
return bs[0:i]
|
||||
}
|
||||
|
||||
// DecodeTime decodes a []byte into a time.Time.
|
||||
func decodeTime(bs []byte) (tt time.Time, err error) {
|
||||
bd := bs[0]
|
||||
var (
|
||||
tsec int64
|
||||
tnsec uint32
|
||||
tz uint16
|
||||
i byte = 1
|
||||
i2 byte
|
||||
n byte
|
||||
)
|
||||
if bd&(1<<7) != 0 {
|
||||
var btmp [8]byte
|
||||
n = ((bd >> 2) & 0x7) + 1
|
||||
i2 = i + n
|
||||
copy(btmp[8-n:], bs[i:i2])
|
||||
//if first bit of bs[i] is set, then fill btmp[0..8-n] with 0xff (ie sign extend it)
|
||||
if bs[i]&(1<<7) != 0 {
|
||||
copy(btmp[0:8-n], bsAll0xff)
|
||||
//for j,k := byte(0), 8-n; j < k; j++ { btmp[j] = 0xff }
|
||||
}
|
||||
i = i2
|
||||
tsec = int64(bigen.Uint64(btmp[:]))
|
||||
}
|
||||
if bd&(1<<6) != 0 {
|
||||
var btmp [4]byte
|
||||
n = (bd & 0x3) + 1
|
||||
i2 = i + n
|
||||
copy(btmp[4-n:], bs[i:i2])
|
||||
i = i2
|
||||
tnsec = bigen.Uint32(btmp[:])
|
||||
}
|
||||
if bd&(1<<5) == 0 {
|
||||
tt = time.Unix(tsec, int64(tnsec)).UTC()
|
||||
return
|
||||
}
|
||||
// In stdlib time.Parse, when a date is parsed without a zone name, it uses "" as zone name.
|
||||
// However, we need name here, so it can be shown when time is printed.
|
||||
// Zone name is in form: UTC-08:00.
|
||||
// Note that Go Libs do not give access to dst flag, so we ignore dst bits
|
||||
|
||||
i2 = i + 2
|
||||
tz = bigen.Uint16(bs[i:i2])
|
||||
i = i2
|
||||
// sign extend sign bit into top 2 MSB (which were dst bits):
|
||||
if tz&(1<<13) == 0 { // positive
|
||||
tz = tz & 0x3fff //clear 2 MSBs: dst bits
|
||||
} else { // negative
|
||||
tz = tz | 0xc000 //set 2 MSBs: dst bits
|
||||
//tzname[3] = '-' (TODO: verify. this works here)
|
||||
}
|
||||
tzint := int16(tz)
|
||||
if tzint == 0 {
|
||||
tt = time.Unix(tsec, int64(tnsec)).UTC()
|
||||
} else {
|
||||
// For Go Time, do not use a descriptive timezone.
|
||||
// It's unnecessary, and makes it harder to do a reflect.DeepEqual.
|
||||
// The Offset already tells what the offset should be, if not on UTC and unknown zone name.
|
||||
// var zoneName = timeLocUTCName(tzint)
|
||||
tt = time.Unix(tsec, int64(tnsec)).In(time.FixedZone("", int(tzint)*60))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func timeLocUTCName(tzint int16) string {
|
||||
if tzint == 0 {
|
||||
return "UTC"
|
||||
}
|
||||
var tzname = []byte("UTC+00:00")
|
||||
//tzname := fmt.Sprintf("UTC%s%02d:%02d", tzsign, tz/60, tz%60) //perf issue using Sprintf. inline below.
|
||||
//tzhr, tzmin := tz/60, tz%60 //faster if u convert to int first
|
||||
var tzhr, tzmin int16
|
||||
if tzint < 0 {
|
||||
tzname[3] = '-' // (TODO: verify. this works here)
|
||||
tzhr, tzmin = -tzint/60, (-tzint)%60
|
||||
} else {
|
||||
tzhr, tzmin = tzint/60, tzint%60
|
||||
}
|
||||
tzname[4] = timeDigits[tzhr/10]
|
||||
tzname[5] = timeDigits[tzhr%10]
|
||||
tzname[7] = timeDigits[tzmin/10]
|
||||
tzname[8] = timeDigits[tzmin%10]
|
||||
return string(tzname)
|
||||
//return time.FixedZone(string(tzname), int(tzint)*60)
|
||||
}
|
||||
373
vendor/github.com/hashicorp/raft-snapshot/LICENSE
generated
vendored
Normal file
373
vendor/github.com/hashicorp/raft-snapshot/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
1
vendor/github.com/hashicorp/raft-snapshot/README.md
generated
vendored
Normal file
1
vendor/github.com/hashicorp/raft-snapshot/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# raft-snapshot
|
||||
235
vendor/github.com/hashicorp/raft-snapshot/archive.go
generated
vendored
Normal file
235
vendor/github.com/hashicorp/raft-snapshot/archive.go
generated
vendored
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
// The archive utilities manage the internal format of a snapshot, which is a
|
||||
// tar file with the following contents:
|
||||
//
|
||||
// meta.json - JSON-encoded snapshot metadata from Raft
|
||||
// state.bin - Encoded snapshot data from Raft
|
||||
// SHA256SUMS - SHA-256 sums of the above two files
|
||||
//
|
||||
// The integrity information is automatically created and checked, and a failure
|
||||
// there just looks like an error to the caller.
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/raft"
|
||||
)
|
||||
|
||||
// hashList manages a list of filenames and their hashes.
|
||||
type hashList struct {
|
||||
hashes map[string]hash.Hash
|
||||
}
|
||||
|
||||
// newHashList returns a new hashList.
|
||||
func newHashList() *hashList {
|
||||
return &hashList{
|
||||
hashes: make(map[string]hash.Hash),
|
||||
}
|
||||
}
|
||||
|
||||
// Add creates a new hash for the given file.
|
||||
func (hl *hashList) Add(file string) hash.Hash {
|
||||
if existing, ok := hl.hashes[file]; ok {
|
||||
return existing
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
hl.hashes[file] = h
|
||||
return h
|
||||
}
|
||||
|
||||
// Encode takes the current sum of all the hashes and saves the hash list as a
|
||||
// SHA256SUMS-style text file.
|
||||
func (hl *hashList) Encode(w io.Writer) error {
|
||||
for file, h := range hl.hashes {
|
||||
if _, err := fmt.Fprintf(w, "%x %s\n", h.Sum([]byte{}), file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeAndVerify reads a SHA256SUMS-style text file and checks the results
|
||||
// against the current sums for all the hashes.
|
||||
func (hl *hashList) DecodeAndVerify(r io.Reader) error {
|
||||
// Read the file and make sure everything in there has a matching hash.
|
||||
seen := make(map[string]struct{})
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
sha := make([]byte, sha256.Size)
|
||||
var file string
|
||||
if _, err := fmt.Sscanf(s.Text(), "%x %s", &sha, &file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, ok := hl.hashes[file]
|
||||
if !ok {
|
||||
return fmt.Errorf("list missing hash for %q", file)
|
||||
}
|
||||
if !bytes.Equal(sha, h.Sum([]byte{})) {
|
||||
return fmt.Errorf("hash check failed for %q", file)
|
||||
}
|
||||
seen[file] = struct{}{}
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure everything we had a hash for was seen.
|
||||
for file := range hl.hashes {
|
||||
if _, ok := seen[file]; !ok {
|
||||
return fmt.Errorf("file missing for %q", file)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// write takes a writer and creates an archive with the snapshot metadata,
|
||||
// the snapshot itself, and adds some integrity checking information.
|
||||
func write(out io.Writer, metadata *raft.SnapshotMeta, snap io.Reader) error {
|
||||
// Start a new tarball.
|
||||
now := time.Now()
|
||||
archive := tar.NewWriter(out)
|
||||
|
||||
// Create a hash list that we will use to write a SHA256SUMS file into
|
||||
// the archive.
|
||||
hl := newHashList()
|
||||
|
||||
// Encode the snapshot metadata, which we need to feed back during a
|
||||
// restore.
|
||||
metaHash := hl.Add("meta.json")
|
||||
var metaBuffer bytes.Buffer
|
||||
enc := json.NewEncoder(&metaBuffer)
|
||||
if err := enc.Encode(metadata); err != nil {
|
||||
return fmt.Errorf("failed to encode snapshot metadata: %v", err)
|
||||
}
|
||||
if err := archive.WriteHeader(&tar.Header{
|
||||
Name: "meta.json",
|
||||
Mode: 0600,
|
||||
Size: int64(metaBuffer.Len()),
|
||||
ModTime: now,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to write snapshot metadata header: %v", err)
|
||||
}
|
||||
if _, err := io.Copy(archive, io.TeeReader(&metaBuffer, metaHash)); err != nil {
|
||||
return fmt.Errorf("failed to write snapshot metadata: %v", err)
|
||||
}
|
||||
|
||||
// Copy the snapshot data given the size from the metadata.
|
||||
snapHash := hl.Add("state.bin")
|
||||
if err := archive.WriteHeader(&tar.Header{
|
||||
Name: "state.bin",
|
||||
Mode: 0600,
|
||||
Size: metadata.Size,
|
||||
ModTime: now,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to write snapshot data header: %v", err)
|
||||
}
|
||||
if _, err := io.CopyN(archive, io.TeeReader(snap, snapHash), metadata.Size); err != nil {
|
||||
return fmt.Errorf("failed to write snapshot metadata: %v", err)
|
||||
}
|
||||
|
||||
// Create a SHA256SUMS file that we can use to verify on restore.
|
||||
var shaBuffer bytes.Buffer
|
||||
if err := hl.Encode(&shaBuffer); err != nil {
|
||||
return fmt.Errorf("failed to encode snapshot hashes: %v", err)
|
||||
}
|
||||
if err := archive.WriteHeader(&tar.Header{
|
||||
Name: "SHA256SUMS",
|
||||
Mode: 0600,
|
||||
Size: int64(shaBuffer.Len()),
|
||||
ModTime: now,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to write snapshot hashes header: %v", err)
|
||||
}
|
||||
if _, err := io.Copy(archive, &shaBuffer); err != nil {
|
||||
return fmt.Errorf("failed to write snapshot metadata: %v", err)
|
||||
}
|
||||
|
||||
// Finalize the archive.
|
||||
if err := archive.Close(); err != nil {
|
||||
return fmt.Errorf("failed to finalize snapshot: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// read takes a reader and extracts the snapshot metadata and the snapshot
|
||||
// itself, and also checks the integrity of the data. You must arrange to call
|
||||
// Close() on the returned object or else you will leak a temporary file.
|
||||
func read(in io.Reader, metadata *raft.SnapshotMeta, snap io.Writer) error {
|
||||
// Start a new tar reader.
|
||||
archive := tar.NewReader(in)
|
||||
|
||||
// Create a hash list that we will use to compare with the SHA256SUMS
|
||||
// file in the archive.
|
||||
hl := newHashList()
|
||||
|
||||
// Populate the hashes for all the files we expect to see. The check at
|
||||
// the end will make sure these are all present in the SHA256SUMS file
|
||||
// and that the hashes match.
|
||||
metaHash := hl.Add("meta.json")
|
||||
snapHash := hl.Add("state.bin")
|
||||
|
||||
// Look through the archive for the pieces we care about.
|
||||
var shaBuffer bytes.Buffer
|
||||
for {
|
||||
hdr, err := archive.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading snapshot: %v", err)
|
||||
}
|
||||
|
||||
switch hdr.Name {
|
||||
case "meta.json":
|
||||
// Previously we used json.Decode to decode the archive stream. There are
|
||||
// edgecases in which it doesn't read all the bytes from the stream, even
|
||||
// though the json object is still being parsed properly. Since we
|
||||
// simutaniously feeded everything to metaHash, our hash ended up being
|
||||
// different than what we calculated when creating the snapshot. Which in
|
||||
// turn made the snapshot verification fail. By explicitly reading the
|
||||
// whole thing first we ensure that we calculate the correct hash
|
||||
// independent of how json.Decode works internally.
|
||||
buf, err := ioutil.ReadAll(io.TeeReader(archive, metaHash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read snapshot metadata: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(buf, &metadata); err != nil {
|
||||
return fmt.Errorf("failed to decode snapshot metadata: %v", err)
|
||||
}
|
||||
|
||||
case "state.bin":
|
||||
if _, err := io.Copy(io.MultiWriter(snap, snapHash), archive); err != nil {
|
||||
return fmt.Errorf("failed to read or write snapshot data: %v", err)
|
||||
}
|
||||
|
||||
case "SHA256SUMS":
|
||||
if _, err := io.Copy(&shaBuffer, archive); err != nil {
|
||||
return fmt.Errorf("failed to read snapshot hashes: %v", err)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected file %q in snapshot", hdr.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Verify all the hashes.
|
||||
if err := hl.DecodeAndVerify(&shaBuffer); err != nil {
|
||||
return fmt.Errorf("failed checking integrity of snapshot: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
11
vendor/github.com/hashicorp/raft-snapshot/go.mod
generated
vendored
Normal file
11
vendor/github.com/hashicorp/raft-snapshot/go.mod
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module github.com/hashicorp/raft-snapshot
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
||||
github.com/hashicorp/consul/sdk v0.1.1
|
||||
github.com/hashicorp/go-hclog v0.9.2
|
||||
github.com/hashicorp/go-msgpack v0.5.5
|
||||
github.com/hashicorp/raft v1.0.1
|
||||
)
|
||||
45
vendor/github.com/hashicorp/raft-snapshot/go.sum
generated
vendored
Normal file
45
vendor/github.com/hashicorp/raft-snapshot/go.sum
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/raft v1.0.1 h1:94uRdS11oEneUkxmXq6Vg9shNhBILh2UTb9crQjJWl0=
|
||||
github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
192
vendor/github.com/hashicorp/raft-snapshot/snapshot.go
generated
vendored
Normal file
192
vendor/github.com/hashicorp/raft-snapshot/snapshot.go
generated
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// snapshot manages the interactions between Consul and Raft in order to take
|
||||
// and restore snapshots for disaster recovery. The internal format of a
|
||||
// snapshot is simply a tar file, as described in archive.go.
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/raft"
|
||||
)
|
||||
|
||||
// Snapshot is a structure that holds state about a temporary file that is used
|
||||
// to hold a snapshot. By using an intermediate file we avoid holding everything
|
||||
// in memory.
|
||||
type Snapshot struct {
|
||||
file *os.File
|
||||
index uint64
|
||||
}
|
||||
|
||||
// New takes a state snapshot of the given Raft instance into a temporary file
|
||||
// and returns an object that gives access to the file as an io.Reader. You must
|
||||
// arrange to call Close() on the returned object or else you will leak a
|
||||
// temporary file.
|
||||
func New(logger log.Logger, r *raft.Raft) (*Snapshot, error) {
|
||||
// Take the snapshot.
|
||||
future := r.Snapshot()
|
||||
if err := future.Error(); err != nil {
|
||||
return nil, fmt.Errorf("Raft error when taking snapshot: %v", err)
|
||||
}
|
||||
|
||||
// Open up the snapshot.
|
||||
metadata, snap, err := future.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open snapshot: %v:", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := snap.Close(); err != nil {
|
||||
logger.Error("failed to close Raft snapshot", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make a scratch file to receive the contents so that we don't buffer
|
||||
// everything in memory. This gets deleted in Close() since we keep it
|
||||
// around for re-reading.
|
||||
archive, err := ioutil.TempFile("", "snapshot")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create snapshot file: %v", err)
|
||||
}
|
||||
|
||||
// If anything goes wrong after this point, we will attempt to clean up
|
||||
// the temp file. The happy path will disarm this.
|
||||
var keep bool
|
||||
defer func() {
|
||||
if keep {
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Remove(archive.Name()); err != nil {
|
||||
logger.Error("failed to clean up temp snapshot", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wrap the file writer in a gzip compressor.
|
||||
compressor := gzip.NewWriter(archive)
|
||||
|
||||
// Write the archive.
|
||||
if err := write(compressor, metadata, snap); err != nil {
|
||||
return nil, fmt.Errorf("failed to write snapshot file: %v", err)
|
||||
}
|
||||
|
||||
// Finish the compressed stream.
|
||||
if err := compressor.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to compress snapshot file: %v", err)
|
||||
}
|
||||
|
||||
// Sync the compressed file and rewind it so it's ready to be streamed
|
||||
// out by the caller.
|
||||
if err := archive.Sync(); err != nil {
|
||||
return nil, fmt.Errorf("failed to sync snapshot: %v", err)
|
||||
}
|
||||
if _, err := archive.Seek(0, 0); err != nil {
|
||||
return nil, fmt.Errorf("failed to rewind snapshot: %v", err)
|
||||
}
|
||||
|
||||
keep = true
|
||||
return &Snapshot{archive, metadata.Index}, nil
|
||||
}
|
||||
|
||||
// Index returns the index of the snapshot. This is safe to call on a nil
|
||||
// snapshot, it will just return 0.
|
||||
func (s *Snapshot) Index() uint64 {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
return s.index
|
||||
}
|
||||
|
||||
// Read passes through to the underlying snapshot file. This is safe to call on
|
||||
// a nil snapshot, it will just return an EOF.
|
||||
func (s *Snapshot) Read(p []byte) (n int, err error) {
|
||||
if s == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return s.file.Read(p)
|
||||
}
|
||||
|
||||
// Close closes the snapshot and removes any temporary storage associated with
|
||||
// it. You must arrange to call this whenever NewSnapshot() has been called
|
||||
// successfully. This is safe to call on a nil snapshot.
|
||||
func (s *Snapshot) Close() error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(s.file.Name())
|
||||
}
|
||||
|
||||
// Verify takes the snapshot from the reader and verifies its contents.
|
||||
func Verify(in io.Reader) (*raft.SnapshotMeta, error) {
|
||||
// Wrap the reader in a gzip decompressor.
|
||||
decomp, err := gzip.NewReader(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decompress snapshot: %v", err)
|
||||
}
|
||||
defer decomp.Close()
|
||||
|
||||
// Read the archive, throwing away the snapshot data.
|
||||
var metadata raft.SnapshotMeta
|
||||
if err := read(decomp, &metadata, ioutil.Discard); err != nil {
|
||||
return nil, fmt.Errorf("failed to read snapshot file: %v", err)
|
||||
}
|
||||
return &metadata, nil
|
||||
}
|
||||
|
||||
// Restore takes the snapshot from the reader and attempts to apply it to the
|
||||
// given Raft instance.
|
||||
func Restore(logger log.Logger, in io.Reader, r *raft.Raft) error {
|
||||
// Wrap the reader in a gzip decompressor.
|
||||
decomp, err := gzip.NewReader(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decompress snapshot: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := decomp.Close(); err != nil {
|
||||
logger.Error("failed to close snapshot decompressor", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make a scratch file to receive the contents of the snapshot data so
|
||||
// we can avoid buffering in memory.
|
||||
snap, err := ioutil.TempFile("", "snapshot")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp snapshot file: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := snap.Close(); err != nil {
|
||||
logger.Error("failed to close temp snapshot", "error", err)
|
||||
}
|
||||
if err := os.Remove(snap.Name()); err != nil {
|
||||
logger.Error("failed to clean up temp snapshot", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Read the archive.
|
||||
var metadata raft.SnapshotMeta
|
||||
if err := read(decomp, &metadata, snap); err != nil {
|
||||
return fmt.Errorf("failed to read snapshot file: %v", err)
|
||||
}
|
||||
|
||||
// Sync and rewind the file so it's ready to be read again.
|
||||
if err := snap.Sync(); err != nil {
|
||||
return fmt.Errorf("failed to sync temp snapshot: %v", err)
|
||||
}
|
||||
if _, err := snap.Seek(0, 0); err != nil {
|
||||
return fmt.Errorf("failed to rewind temp snapshot: %v", err)
|
||||
}
|
||||
|
||||
// Feed the snapshot into Raft.
|
||||
if err := r.Restore(&metadata, snap, 0); err != nil {
|
||||
return fmt.Errorf("Raft error when restoring snapshot: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
23
vendor/github.com/hashicorp/raft/.gitignore
generated
vendored
Normal file
23
vendor/github.com/hashicorp/raft/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
18
vendor/github.com/hashicorp/raft/.travis.yml
generated
vendored
Normal file
18
vendor/github.com/hashicorp/raft/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
# Disabled until https://github.com/armon/go-metrics/issues/59 is fixed
|
||||
# - 1.6
|
||||
- 1.8
|
||||
- 1.9
|
||||
- 1.12
|
||||
- tip
|
||||
|
||||
install: make deps
|
||||
script:
|
||||
- make integ
|
||||
|
||||
notifications:
|
||||
flowdock:
|
||||
secure: fZrcf9rlh2IrQrlch1sHkn3YI7SKvjGnAl/zyV5D6NROe1Bbr6d3QRMuCXWWdhJHzjKmXk5rIzbqJhUc0PNF7YjxGNKSzqWMQ56KcvN1k8DzlqxpqkcA3Jbs6fXCWo2fssRtZ7hj/wOP1f5n6cc7kzHDt9dgaYJ6nO2fqNPJiTc=
|
||||
|
||||
35
vendor/github.com/hashicorp/raft/CHANGELOG.md
generated
vendored
Normal file
35
vendor/github.com/hashicorp/raft/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# UNRELEASED
|
||||
|
||||
# 1.1.0 (Mai 23rd, 2019)
|
||||
|
||||
FEATURES
|
||||
|
||||
* Add transfer leadership extension [[GH-306](https://github.com/hashicorp/raft/pull/306)]
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* Move to `go mod` [[GH-323](https://github.com/hashicorp/consul/pull/323)]
|
||||
* Leveled log [[GH-321](https://github.com/hashicorp/consul/pull/321)]
|
||||
* Add peer changes to observations [[GH-326](https://github.com/hashicorp/consul/pull/326)]
|
||||
|
||||
BUGFIXES
|
||||
|
||||
* Copy the contents of an InmemSnapshotStore when opening a snapshot [[GH-270](https://github.com/hashicorp/consul/pull/270)]
|
||||
* Fix logging panic when converting parameters to strings [[GH-332](https://github.com/hashicorp/consul/pull/332)]
|
||||
|
||||
# 1.0.1 (April 12th, 2019)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* InMemTransport: Add timeout for sending a message [[GH-313](https://github.com/hashicorp/raft/pull/313)]
|
||||
* ensure 'make deps' downloads test dependencies like testify [[GH-310](https://github.com/hashicorp/raft/pull/310)]
|
||||
* Clarifies function of CommitTimeout [[GH-309](https://github.com/hashicorp/raft/pull/309)]
|
||||
* Add additional metrics regarding log dispatching and committal [[GH-316](https://github.com/hashicorp/raft/pull/316)]
|
||||
|
||||
# 1.0.0 (October 3rd, 2017)
|
||||
|
||||
v1.0.0 takes the changes that were staged in the library-v2-stage-one branch. This version manages server identities using a UUID, so introduces some breaking API changes. It also versions the Raft protocol, and requires some special steps when interoperating with Raft servers running older versions of the library (see the detailed comment in config.go about version compatibility). You can reference https://github.com/hashicorp/consul/pull/2222 for an idea of what was required to port Consul to these new interfaces.
|
||||
|
||||
# 0.1.0 (September 29th, 2017)
|
||||
|
||||
v0.1.0 is the original stable version of the library that was in master and has been maintained with no breaking API changes. This was in use by Consul prior to version 0.7.0.
|
||||
354
vendor/github.com/hashicorp/raft/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/raft/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
||||
27
vendor/github.com/hashicorp/raft/Makefile
generated
vendored
Normal file
27
vendor/github.com/hashicorp/raft/Makefile
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...)
|
||||
TEST_RESULTS_DIR?=/tmp/test-results
|
||||
|
||||
test:
|
||||
go test -timeout=60s -race .
|
||||
|
||||
integ: test
|
||||
INTEG_TESTS=yes go test -timeout=25s -run=Integ .
|
||||
|
||||
ci.test:
|
||||
gotestsum --format=short-verbose --junitfile $(TEST_RESULTS_DIR)/gotestsum-report-test.xml -- -timeout=60s -race .
|
||||
|
||||
ci.integ: ci.test
|
||||
INTEG_TESTS=yes gotestsum --format=short-verbose --junitfile $(TEST_RESULTS_DIR)/gotestsum-report-integ.xml -- -timeout=25s -run=Integ .
|
||||
|
||||
fuzz:
|
||||
go test -timeout=300s ./fuzzy
|
||||
|
||||
deps:
|
||||
go get -t -d -v ./...
|
||||
echo $(DEPS) | xargs -n1 go get -d
|
||||
|
||||
cov:
|
||||
INTEG_TESTS=yes gocov test github.com/hashicorp/raft | gocov-html > /tmp/coverage.html
|
||||
open /tmp/coverage.html
|
||||
|
||||
.PHONY: test cov integ deps
|
||||
107
vendor/github.com/hashicorp/raft/README.md
generated
vendored
Normal file
107
vendor/github.com/hashicorp/raft/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
raft [](https://travis-ci.org/hashicorp/raft) [](https://circleci.com/gh/hashicorp/raft)
|
||||
====
|
||||
|
||||
raft is a [Go](http://www.golang.org) library that manages a replicated
|
||||
log and can be used with an FSM to manage replicated state machines. It
|
||||
is a library for providing [consensus](http://en.wikipedia.org/wiki/Consensus_(computer_science)).
|
||||
|
||||
The use cases for such a library are far-reaching as replicated state
|
||||
machines are a key component of many distributed systems. They enable
|
||||
building Consistent, Partition Tolerant (CP) systems, with limited
|
||||
fault tolerance as well.
|
||||
|
||||
## Building
|
||||
|
||||
If you wish to build raft you'll need Go version 1.2+ installed.
|
||||
|
||||
Please check your installation with:
|
||||
|
||||
```
|
||||
go version
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/raft).
|
||||
|
||||
To prevent complications with cgo, the primary backend `MDBStore` is in a separate repository,
|
||||
called [raft-mdb](http://github.com/hashicorp/raft-mdb). That is the recommended implementation
|
||||
for the `LogStore` and `StableStore`.
|
||||
|
||||
A pure Go backend using [BoltDB](https://github.com/boltdb/bolt) is also available called
|
||||
[raft-boltdb](https://github.com/hashicorp/raft-boltdb). It can also be used as a `LogStore`
|
||||
and `StableStore`.
|
||||
|
||||
## Tagged Releases
|
||||
|
||||
As of September 2017, HashiCorp will start using tags for this library to clearly indicate
|
||||
major version updates. We recommend you vendor your application's dependency on this library.
|
||||
|
||||
* v0.1.0 is the original stable version of the library that was in master and has been maintained
|
||||
with no breaking API changes. This was in use by Consul prior to version 0.7.0.
|
||||
|
||||
* v1.0.0 takes the changes that were staged in the library-v2-stage-one branch. This version
|
||||
manages server identities using a UUID, so introduces some breaking API changes. It also versions
|
||||
the Raft protocol, and requires some special steps when interoperating with Raft servers running
|
||||
older versions of the library (see the detailed comment in config.go about version compatibility).
|
||||
You can reference https://github.com/hashicorp/consul/pull/2222 for an idea of what was required
|
||||
to port Consul to these new interfaces.
|
||||
|
||||
This version includes some new features as well, including non voting servers, a new address
|
||||
provider abstraction in the transport layer, and more resilient snapshots.
|
||||
|
||||
## Protocol
|
||||
|
||||
raft is based on ["Raft: In Search of an Understandable Consensus Algorithm"](https://ramcloud.stanford.edu/wiki/download/attachments/11370504/raft.pdf)
|
||||
|
||||
A high level overview of the Raft protocol is described below, but for details please read the full
|
||||
[Raft paper](https://ramcloud.stanford.edu/wiki/download/attachments/11370504/raft.pdf)
|
||||
followed by the raft source. Any questions about the raft protocol should be sent to the
|
||||
[raft-dev mailing list](https://groups.google.com/forum/#!forum/raft-dev).
|
||||
|
||||
### Protocol Description
|
||||
|
||||
Raft nodes are always in one of three states: follower, candidate or leader. All
|
||||
nodes initially start out as a follower. In this state, nodes can accept log entries
|
||||
from a leader and cast votes. If no entries are received for some time, nodes
|
||||
self-promote to the candidate state. In the candidate state nodes request votes from
|
||||
their peers. If a candidate receives a quorum of votes, then it is promoted to a leader.
|
||||
The leader must accept new log entries and replicate to all the other followers.
|
||||
In addition, if stale reads are not acceptable, all queries must also be performed on
|
||||
the leader.
|
||||
|
||||
Once a cluster has a leader, it is able to accept new log entries. A client can
|
||||
request that a leader append a new log entry, which is an opaque binary blob to
|
||||
Raft. The leader then writes the entry to durable storage and attempts to replicate
|
||||
to a quorum of followers. Once the log entry is considered *committed*, it can be
|
||||
*applied* to a finite state machine. The finite state machine is application specific,
|
||||
and is implemented using an interface.
|
||||
|
||||
An obvious question relates to the unbounded nature of a replicated log. Raft provides
|
||||
a mechanism by which the current state is snapshotted, and the log is compacted. Because
|
||||
of the FSM abstraction, restoring the state of the FSM must result in the same state
|
||||
as a replay of old logs. This allows Raft to capture the FSM state at a point in time,
|
||||
and then remove all the logs that were used to reach that state. This is performed automatically
|
||||
without user intervention, and prevents unbounded disk usage as well as minimizing
|
||||
time spent replaying logs.
|
||||
|
||||
Lastly, there is the issue of updating the peer set when new servers are joining
|
||||
or existing servers are leaving. As long as a quorum of nodes is available, this
|
||||
is not an issue as Raft provides mechanisms to dynamically update the peer set.
|
||||
If a quorum of nodes is unavailable, then this becomes a very challenging issue.
|
||||
For example, suppose there are only 2 peers, A and B. The quorum size is also
|
||||
2, meaning both nodes must agree to commit a log entry. If either A or B fails,
|
||||
it is now impossible to reach quorum. This means the cluster is unable to add,
|
||||
or remove a node, or commit any additional log entries. This results in *unavailability*.
|
||||
At this point, manual intervention would be required to remove either A or B,
|
||||
and to restart the remaining node in bootstrap mode.
|
||||
|
||||
A Raft cluster of 3 nodes can tolerate a single node failure, while a cluster
|
||||
of 5 can tolerate 2 node failures. The recommended configuration is to either
|
||||
run 3 or 5 raft servers. This maximizes availability without
|
||||
greatly sacrificing performance.
|
||||
|
||||
In terms of performance, Raft is comparable to Paxos. Assuming stable leadership,
|
||||
committing a log entry requires a single round trip to half of the cluster.
|
||||
Thus performance is bound by disk I/O and network latency.
|
||||
|
||||
1078
vendor/github.com/hashicorp/raft/api.go
generated
vendored
Normal file
1078
vendor/github.com/hashicorp/raft/api.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
177
vendor/github.com/hashicorp/raft/commands.go
generated
vendored
Normal file
177
vendor/github.com/hashicorp/raft/commands.go
generated
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
package raft
|
||||
|
||||
// RPCHeader is a common sub-structure used to pass along protocol version and
|
||||
// other information about the cluster. For older Raft implementations before
|
||||
// versioning was added this will default to a zero-valued structure when read
|
||||
// by newer Raft versions.
|
||||
type RPCHeader struct {
|
||||
// ProtocolVersion is the version of the protocol the sender is
|
||||
// speaking.
|
||||
ProtocolVersion ProtocolVersion
|
||||
}
|
||||
|
||||
// WithRPCHeader is an interface that exposes the RPC header.
|
||||
type WithRPCHeader interface {
|
||||
GetRPCHeader() RPCHeader
|
||||
}
|
||||
|
||||
// AppendEntriesRequest is the command used to append entries to the
|
||||
// replicated log.
|
||||
type AppendEntriesRequest struct {
|
||||
RPCHeader
|
||||
|
||||
// Provide the current term and leader
|
||||
Term uint64
|
||||
Leader []byte
|
||||
|
||||
// Provide the previous entries for integrity checking
|
||||
PrevLogEntry uint64
|
||||
PrevLogTerm uint64
|
||||
|
||||
// New entries to commit
|
||||
Entries []*Log
|
||||
|
||||
// Commit index on the leader
|
||||
LeaderCommitIndex uint64
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *AppendEntriesRequest) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// AppendEntriesResponse is the response returned from an
|
||||
// AppendEntriesRequest.
|
||||
type AppendEntriesResponse struct {
|
||||
RPCHeader
|
||||
|
||||
// Newer term if leader is out of date
|
||||
Term uint64
|
||||
|
||||
// Last Log is a hint to help accelerate rebuilding slow nodes
|
||||
LastLog uint64
|
||||
|
||||
// We may not succeed if we have a conflicting entry
|
||||
Success bool
|
||||
|
||||
// There are scenarios where this request didn't succeed
|
||||
// but there's no need to wait/back-off the next attempt.
|
||||
NoRetryBackoff bool
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *AppendEntriesResponse) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// RequestVoteRequest is the command used by a candidate to ask a Raft peer
|
||||
// for a vote in an election.
|
||||
type RequestVoteRequest struct {
|
||||
RPCHeader
|
||||
|
||||
// Provide the term and our id
|
||||
Term uint64
|
||||
Candidate []byte
|
||||
|
||||
// Used to ensure safety
|
||||
LastLogIndex uint64
|
||||
LastLogTerm uint64
|
||||
|
||||
// Used to indicate to peers if this vote was triggered by a leadership
|
||||
// transfer. It is required for leadership transfer to work, because servers
|
||||
// wouldn't vote otherwise if they are aware of an existing leader.
|
||||
LeadershipTransfer bool
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *RequestVoteRequest) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// RequestVoteResponse is the response returned from a RequestVoteRequest.
|
||||
type RequestVoteResponse struct {
|
||||
RPCHeader
|
||||
|
||||
// Newer term if leader is out of date.
|
||||
Term uint64
|
||||
|
||||
// Peers is deprecated, but required by servers that only understand
|
||||
// protocol version 0. This is not populated in protocol version 2
|
||||
// and later.
|
||||
Peers []byte
|
||||
|
||||
// Is the vote granted.
|
||||
Granted bool
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *RequestVoteResponse) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// InstallSnapshotRequest is the command sent to a Raft peer to bootstrap its
|
||||
// log (and state machine) from a snapshot on another peer.
|
||||
type InstallSnapshotRequest struct {
|
||||
RPCHeader
|
||||
SnapshotVersion SnapshotVersion
|
||||
|
||||
Term uint64
|
||||
Leader []byte
|
||||
|
||||
// These are the last index/term included in the snapshot
|
||||
LastLogIndex uint64
|
||||
LastLogTerm uint64
|
||||
|
||||
// Peer Set in the snapshot. This is deprecated in favor of Configuration
|
||||
// but remains here in case we receive an InstallSnapshot from a leader
|
||||
// that's running old code.
|
||||
Peers []byte
|
||||
|
||||
// Cluster membership.
|
||||
Configuration []byte
|
||||
// Log index where 'Configuration' entry was originally written.
|
||||
ConfigurationIndex uint64
|
||||
|
||||
// Size of the snapshot
|
||||
Size int64
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *InstallSnapshotRequest) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// InstallSnapshotResponse is the response returned from an
|
||||
// InstallSnapshotRequest.
|
||||
type InstallSnapshotResponse struct {
|
||||
RPCHeader
|
||||
|
||||
Term uint64
|
||||
Success bool
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *InstallSnapshotResponse) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// TimeoutNowRequest is the command used by a leader to signal another server to
|
||||
// start an election.
|
||||
type TimeoutNowRequest struct {
|
||||
RPCHeader
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *TimeoutNowRequest) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
|
||||
// TimeoutNowResponse is the response to TimeoutNowRequest.
|
||||
type TimeoutNowResponse struct {
|
||||
RPCHeader
|
||||
}
|
||||
|
||||
// See WithRPCHeader.
|
||||
func (r *TimeoutNowResponse) GetRPCHeader() RPCHeader {
|
||||
return r.RPCHeader
|
||||
}
|
||||
101
vendor/github.com/hashicorp/raft/commitment.go
generated
vendored
Normal file
101
vendor/github.com/hashicorp/raft/commitment.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Commitment is used to advance the leader's commit index. The leader and
|
||||
// replication goroutines report in newly written entries with Match(), and
|
||||
// this notifies on commitCh when the commit index has advanced.
|
||||
type commitment struct {
|
||||
// protects matchIndexes and commitIndex
|
||||
sync.Mutex
|
||||
// notified when commitIndex increases
|
||||
commitCh chan struct{}
|
||||
// voter ID to log index: the server stores up through this log entry
|
||||
matchIndexes map[ServerID]uint64
|
||||
// a quorum stores up through this log entry. monotonically increases.
|
||||
commitIndex uint64
|
||||
// the first index of this leader's term: this needs to be replicated to a
|
||||
// majority of the cluster before this leader may mark anything committed
|
||||
// (per Raft's commitment rule)
|
||||
startIndex uint64
|
||||
}
|
||||
|
||||
// newCommitment returns an commitment struct that notifies the provided
|
||||
// channel when log entries have been committed. A new commitment struct is
|
||||
// created each time this server becomes leader for a particular term.
|
||||
// 'configuration' is the servers in the cluster.
|
||||
// 'startIndex' is the first index created in this term (see
|
||||
// its description above).
|
||||
func newCommitment(commitCh chan struct{}, configuration Configuration, startIndex uint64) *commitment {
|
||||
matchIndexes := make(map[ServerID]uint64)
|
||||
for _, server := range configuration.Servers {
|
||||
if server.Suffrage == Voter {
|
||||
matchIndexes[server.ID] = 0
|
||||
}
|
||||
}
|
||||
return &commitment{
|
||||
commitCh: commitCh,
|
||||
matchIndexes: matchIndexes,
|
||||
commitIndex: 0,
|
||||
startIndex: startIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a new cluster membership configuration is created: it will be
|
||||
// used to determine commitment from now on. 'configuration' is the servers in
|
||||
// the cluster.
|
||||
func (c *commitment) setConfiguration(configuration Configuration) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
oldMatchIndexes := c.matchIndexes
|
||||
c.matchIndexes = make(map[ServerID]uint64)
|
||||
for _, server := range configuration.Servers {
|
||||
if server.Suffrage == Voter {
|
||||
c.matchIndexes[server.ID] = oldMatchIndexes[server.ID] // defaults to 0
|
||||
}
|
||||
}
|
||||
c.recalculate()
|
||||
}
|
||||
|
||||
// Called by leader after commitCh is notified
|
||||
func (c *commitment) getCommitIndex() uint64 {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.commitIndex
|
||||
}
|
||||
|
||||
// Match is called once a server completes writing entries to disk: either the
|
||||
// leader has written the new entry or a follower has replied to an
|
||||
// AppendEntries RPC. The given server's disk agrees with this server's log up
|
||||
// through the given index.
|
||||
func (c *commitment) match(server ServerID, matchIndex uint64) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if prev, hasVote := c.matchIndexes[server]; hasVote && matchIndex > prev {
|
||||
c.matchIndexes[server] = matchIndex
|
||||
c.recalculate()
|
||||
}
|
||||
}
|
||||
|
||||
// Internal helper to calculate new commitIndex from matchIndexes.
|
||||
// Must be called with lock held.
|
||||
func (c *commitment) recalculate() {
|
||||
if len(c.matchIndexes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
matched := make([]uint64, 0, len(c.matchIndexes))
|
||||
for _, idx := range c.matchIndexes {
|
||||
matched = append(matched, idx)
|
||||
}
|
||||
sort.Sort(uint64Slice(matched))
|
||||
quorumMatchIndex := matched[(len(matched)-1)/2]
|
||||
|
||||
if quorumMatchIndex > c.commitIndex && quorumMatchIndex >= c.startIndex {
|
||||
c.commitIndex = quorumMatchIndex
|
||||
asyncNotifyCh(c.commitCh)
|
||||
}
|
||||
}
|
||||
265
vendor/github.com/hashicorp/raft/config.go
generated
vendored
Normal file
265
vendor/github.com/hashicorp/raft/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
)
|
||||
|
||||
// These are the versions of the protocol (which includes RPC messages as
|
||||
// well as Raft-specific log entries) that this server can _understand_. Use
|
||||
// the ProtocolVersion member of the Config object to control the version of
|
||||
// the protocol to use when _speaking_ to other servers. Note that depending on
|
||||
// the protocol version being spoken, some otherwise understood RPC messages
|
||||
// may be refused. See dispositionRPC for details of this logic.
|
||||
//
|
||||
// There are notes about the upgrade path in the description of the versions
|
||||
// below. If you are starting a fresh cluster then there's no reason not to
|
||||
// jump right to the latest protocol version. If you need to interoperate with
|
||||
// older, version 0 Raft servers you'll need to drive the cluster through the
|
||||
// different versions in order.
|
||||
//
|
||||
// The version details are complicated, but here's a summary of what's required
|
||||
// to get from a version 0 cluster to version 3:
|
||||
//
|
||||
// 1. In version N of your app that starts using the new Raft library with
|
||||
// versioning, set ProtocolVersion to 1.
|
||||
// 2. Make version N+1 of your app require version N as a prerequisite (all
|
||||
// servers must be upgraded). For version N+1 of your app set ProtocolVersion
|
||||
// to 2.
|
||||
// 3. Similarly, make version N+2 of your app require version N+1 as a
|
||||
// prerequisite. For version N+2 of your app, set ProtocolVersion to 3.
|
||||
//
|
||||
// During this upgrade, older cluster members will still have Server IDs equal
|
||||
// to their network addresses. To upgrade an older member and give it an ID, it
|
||||
// needs to leave the cluster and re-enter:
|
||||
//
|
||||
// 1. Remove the server from the cluster with RemoveServer, using its network
|
||||
// address as its ServerID.
|
||||
// 2. Update the server's config to use a UUID or something else that is
|
||||
// not tied to the machine as the ServerID (restarting the server).
|
||||
// 3. Add the server back to the cluster with AddVoter, using its new ID.
|
||||
//
|
||||
// You can do this during the rolling upgrade from N+1 to N+2 of your app, or
|
||||
// as a rolling change at any time after the upgrade.
|
||||
//
|
||||
// Version History
|
||||
//
|
||||
// 0: Original Raft library before versioning was added. Servers running this
|
||||
// version of the Raft library use AddPeerDeprecated/RemovePeerDeprecated
|
||||
// for all configuration changes, and have no support for LogConfiguration.
|
||||
// 1: First versioned protocol, used to interoperate with old servers, and begin
|
||||
// the migration path to newer versions of the protocol. Under this version
|
||||
// all configuration changes are propagated using the now-deprecated
|
||||
// RemovePeerDeprecated Raft log entry. This means that server IDs are always
|
||||
// set to be the same as the server addresses (since the old log entry type
|
||||
// cannot transmit an ID), and only AddPeer/RemovePeer APIs are supported.
|
||||
// Servers running this version of the protocol can understand the new
|
||||
// LogConfiguration Raft log entry but will never generate one so they can
|
||||
// remain compatible with version 0 Raft servers in the cluster.
|
||||
// 2: Transitional protocol used when migrating an existing cluster to the new
|
||||
// server ID system. Server IDs are still set to be the same as server
|
||||
// addresses, but all configuration changes are propagated using the new
|
||||
// LogConfiguration Raft log entry type, which can carry full ID information.
|
||||
// This version supports the old AddPeer/RemovePeer APIs as well as the new
|
||||
// ID-based AddVoter/RemoveServer APIs which should be used when adding
|
||||
// version 3 servers to the cluster later. This version sheds all
|
||||
// interoperability with version 0 servers, but can interoperate with newer
|
||||
// Raft servers running with protocol version 1 since they can understand the
|
||||
// new LogConfiguration Raft log entry, and this version can still understand
|
||||
// their RemovePeerDeprecated Raft log entries. We need this protocol version
|
||||
// as an intermediate step between 1 and 3 so that servers will propagate the
|
||||
// ID information that will come from newly-added (or -rolled) servers using
|
||||
// protocol version 3, but since they are still using their address-based IDs
|
||||
// from the previous step they will still be able to track commitments and
|
||||
// their own voting status properly. If we skipped this step, servers would
|
||||
// be started with their new IDs, but they wouldn't see themselves in the old
|
||||
// address-based configuration, so none of the servers would think they had a
|
||||
// vote.
|
||||
// 3: Protocol adding full support for server IDs and new ID-based server APIs
|
||||
// (AddVoter, AddNonvoter, etc.), old AddPeer/RemovePeer APIs are no longer
|
||||
// supported. Version 2 servers should be swapped out by removing them from
|
||||
// the cluster one-by-one and re-adding them with updated configuration for
|
||||
// this protocol version, along with their server ID. The remove/add cycle
|
||||
// is required to populate their server ID. Note that removing must be done
|
||||
// by ID, which will be the old server's address.
|
||||
type ProtocolVersion int
|
||||
|
||||
const (
|
||||
ProtocolVersionMin ProtocolVersion = 0
|
||||
ProtocolVersionMax = 3
|
||||
)
|
||||
|
||||
// These are versions of snapshots that this server can _understand_. Currently,
|
||||
// it is always assumed that this server generates the latest version, though
|
||||
// this may be changed in the future to include a configurable version.
|
||||
//
|
||||
// Version History
|
||||
//
|
||||
// 0: Original Raft library before versioning was added. The peers portion of
|
||||
// these snapshots is encoded in the legacy format which requires decodePeers
|
||||
// to parse. This version of snapshots should only be produced by the
|
||||
// unversioned Raft library.
|
||||
// 1: New format which adds support for a full configuration structure and its
|
||||
// associated log index, with support for server IDs and non-voting server
|
||||
// modes. To ease upgrades, this also includes the legacy peers structure but
|
||||
// that will never be used by servers that understand version 1 snapshots.
|
||||
// Since the original Raft library didn't enforce any versioning, we must
|
||||
// include the legacy peers structure for this version, but we can deprecate
|
||||
// it in the next snapshot version.
|
||||
type SnapshotVersion int
|
||||
|
||||
const (
|
||||
SnapshotVersionMin SnapshotVersion = 0
|
||||
SnapshotVersionMax = 1
|
||||
)
|
||||
|
||||
// Config provides any necessary configuration for the Raft server.
|
||||
type Config struct {
|
||||
// ProtocolVersion allows a Raft server to inter-operate with older
|
||||
// Raft servers running an older version of the code. This is used to
|
||||
// version the wire protocol as well as Raft-specific log entries that
|
||||
// the server uses when _speaking_ to other servers. There is currently
|
||||
// no auto-negotiation of versions so all servers must be manually
|
||||
// configured with compatible versions. See ProtocolVersionMin and
|
||||
// ProtocolVersionMax for the versions of the protocol that this server
|
||||
// can _understand_.
|
||||
ProtocolVersion ProtocolVersion
|
||||
|
||||
// HeartbeatTimeout specifies the time in follower state without
|
||||
// a leader before we attempt an election.
|
||||
HeartbeatTimeout time.Duration
|
||||
|
||||
// ElectionTimeout specifies the time in candidate state without
|
||||
// a leader before we attempt an election.
|
||||
ElectionTimeout time.Duration
|
||||
|
||||
// CommitTimeout controls the time without an Apply() operation
|
||||
// before we heartbeat to ensure a timely commit. Due to random
|
||||
// staggering, may be delayed as much as 2x this value.
|
||||
CommitTimeout time.Duration
|
||||
|
||||
// MaxAppendEntries controls the maximum number of append entries
|
||||
// to send at once. We want to strike a balance between efficiency
|
||||
// and avoiding waste if the follower is going to reject because of
|
||||
// an inconsistent log.
|
||||
MaxAppendEntries int
|
||||
|
||||
// If we are a member of a cluster, and RemovePeer is invoked for the
|
||||
// local node, then we forget all peers and transition into the follower state.
|
||||
// If ShutdownOnRemove is is set, we additional shutdown Raft. Otherwise,
|
||||
// we can become a leader of a cluster containing only this node.
|
||||
ShutdownOnRemove bool
|
||||
|
||||
// TrailingLogs controls how many logs we leave after a snapshot. This is
|
||||
// used so that we can quickly replay logs on a follower instead of being
|
||||
// forced to send an entire snapshot.
|
||||
TrailingLogs uint64
|
||||
|
||||
// SnapshotInterval controls how often we check if we should perform a snapshot.
|
||||
// We randomly stagger between this value and 2x this value to avoid the entire
|
||||
// cluster from performing a snapshot at once.
|
||||
SnapshotInterval time.Duration
|
||||
|
||||
// SnapshotThreshold controls how many outstanding logs there must be before
|
||||
// we perform a snapshot. This is to prevent excessive snapshots when we can
|
||||
// just replay a small set of logs.
|
||||
SnapshotThreshold uint64
|
||||
|
||||
// LeaderLeaseTimeout is used to control how long the "lease" lasts
|
||||
// for being the leader without being able to contact a quorum
|
||||
// of nodes. If we reach this interval without contact, we will
|
||||
// step down as leader.
|
||||
LeaderLeaseTimeout time.Duration
|
||||
|
||||
// StartAsLeader forces Raft to start in the leader state. This should
|
||||
// never be used except for testing purposes, as it can cause a split-brain.
|
||||
StartAsLeader bool
|
||||
|
||||
// The unique ID for this server across all time. When running with
|
||||
// ProtocolVersion < 3, you must set this to be the same as the network
|
||||
// address of your transport.
|
||||
LocalID ServerID
|
||||
|
||||
// NotifyCh is used to provide a channel that will be notified of leadership
|
||||
// changes. Raft will block writing to this channel, so it should either be
|
||||
// buffered or aggressively consumed.
|
||||
NotifyCh chan<- bool
|
||||
|
||||
// LogOutput is used as a sink for logs, unless Logger is specified.
|
||||
// Defaults to os.Stderr.
|
||||
LogOutput io.Writer
|
||||
|
||||
// LogLevel represents a log level. If a no matching string is specified,
|
||||
// hclog.NoLevel is assumed.
|
||||
LogLevel string
|
||||
|
||||
// Logger is a user-provided hc-log logger. If nil, a logger writing to
|
||||
// LogOutput with LogLevel is used.
|
||||
Logger hclog.Logger
|
||||
}
|
||||
|
||||
// DefaultConfig returns a Config with usable defaults.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
ProtocolVersion: ProtocolVersionMax,
|
||||
HeartbeatTimeout: 1000 * time.Millisecond,
|
||||
ElectionTimeout: 1000 * time.Millisecond,
|
||||
CommitTimeout: 50 * time.Millisecond,
|
||||
MaxAppendEntries: 64,
|
||||
ShutdownOnRemove: true,
|
||||
TrailingLogs: 10240,
|
||||
SnapshotInterval: 120 * time.Second,
|
||||
SnapshotThreshold: 8192,
|
||||
LeaderLeaseTimeout: 500 * time.Millisecond,
|
||||
LogLevel: "DEBUG",
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig is used to validate a sane configuration
|
||||
func ValidateConfig(config *Config) error {
|
||||
// We don't actually support running as 0 in the library any more, but
|
||||
// we do understand it.
|
||||
protocolMin := ProtocolVersionMin
|
||||
if protocolMin == 0 {
|
||||
protocolMin = 1
|
||||
}
|
||||
if config.ProtocolVersion < protocolMin ||
|
||||
config.ProtocolVersion > ProtocolVersionMax {
|
||||
return fmt.Errorf("Protocol version %d must be >= %d and <= %d",
|
||||
config.ProtocolVersion, protocolMin, ProtocolVersionMax)
|
||||
}
|
||||
if len(config.LocalID) == 0 {
|
||||
return fmt.Errorf("LocalID cannot be empty")
|
||||
}
|
||||
if config.HeartbeatTimeout < 5*time.Millisecond {
|
||||
return fmt.Errorf("Heartbeat timeout is too low")
|
||||
}
|
||||
if config.ElectionTimeout < 5*time.Millisecond {
|
||||
return fmt.Errorf("Election timeout is too low")
|
||||
}
|
||||
if config.CommitTimeout < time.Millisecond {
|
||||
return fmt.Errorf("Commit timeout is too low")
|
||||
}
|
||||
if config.MaxAppendEntries <= 0 {
|
||||
return fmt.Errorf("MaxAppendEntries must be positive")
|
||||
}
|
||||
if config.MaxAppendEntries > 1024 {
|
||||
return fmt.Errorf("MaxAppendEntries is too large")
|
||||
}
|
||||
if config.SnapshotInterval < 5*time.Millisecond {
|
||||
return fmt.Errorf("Snapshot interval is too low")
|
||||
}
|
||||
if config.LeaderLeaseTimeout < 5*time.Millisecond {
|
||||
return fmt.Errorf("Leader lease timeout is too low")
|
||||
}
|
||||
if config.LeaderLeaseTimeout > config.HeartbeatTimeout {
|
||||
return fmt.Errorf("Leader lease timeout cannot be larger than heartbeat timeout")
|
||||
}
|
||||
if config.ElectionTimeout < config.HeartbeatTimeout {
|
||||
return fmt.Errorf("Election timeout must be equal or greater than Heartbeat Timeout")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
360
vendor/github.com/hashicorp/raft/configuration.go
generated
vendored
Normal file
360
vendor/github.com/hashicorp/raft/configuration.go
generated
vendored
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
package raft
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ServerSuffrage determines whether a Server in a Configuration gets a vote.
|
||||
type ServerSuffrage int
|
||||
|
||||
// Note: Don't renumber these, since the numbers are written into the log.
|
||||
const (
|
||||
// Voter is a server whose vote is counted in elections and whose match index
|
||||
// is used in advancing the leader's commit index.
|
||||
Voter ServerSuffrage = iota
|
||||
// Nonvoter is a server that receives log entries but is not considered for
|
||||
// elections or commitment purposes.
|
||||
Nonvoter
|
||||
// Staging is a server that acts like a nonvoter with one exception: once a
|
||||
// staging server receives enough log entries to be sufficiently caught up to
|
||||
// the leader's log, the leader will invoke a membership change to change
|
||||
// the Staging server to a Voter.
|
||||
Staging
|
||||
)
|
||||
|
||||
func (s ServerSuffrage) String() string {
|
||||
switch s {
|
||||
case Voter:
|
||||
return "Voter"
|
||||
case Nonvoter:
|
||||
return "Nonvoter"
|
||||
case Staging:
|
||||
return "Staging"
|
||||
}
|
||||
return "ServerSuffrage"
|
||||
}
|
||||
|
||||
// ConfigurationStore provides an interface that can optionally be implemented by FSMs
|
||||
// to store configuration updates made in the replicated log. In general this is only
|
||||
// necessary for FSMs that mutate durable state directly instead of applying changes
|
||||
// in memory and snapshotting periodically. By storing configuration changes, the
|
||||
// persistent FSM state can behave as a complete snapshot, and be able to recover
|
||||
// without an external snapshot just for persisting the raft configuration.
|
||||
type ConfigurationStore interface {
|
||||
// StoreConfiguration is invoked once a log entry containing a configuration
|
||||
// change is committed. It takes the index at which the configuration was
|
||||
// written and the configuration value.
|
||||
StoreConfiguration(index uint64, configuration Configuration)
|
||||
}
|
||||
|
||||
type nopConfigurationStore struct{}
|
||||
|
||||
func (s nopConfigurationStore) StoreConfiguration(_ uint64, _ Configuration) {}
|
||||
|
||||
// ServerID is a unique string identifying a server for all time.
|
||||
type ServerID string
|
||||
|
||||
// ServerAddress is a network address for a server that a transport can contact.
|
||||
type ServerAddress string
|
||||
|
||||
// Server tracks the information about a single server in a configuration.
|
||||
type Server struct {
|
||||
// Suffrage determines whether the server gets a vote.
|
||||
Suffrage ServerSuffrage
|
||||
// ID is a unique string identifying this server for all time.
|
||||
ID ServerID
|
||||
// Address is its network address that a transport can contact.
|
||||
Address ServerAddress
|
||||
}
|
||||
|
||||
// Configuration tracks which servers are in the cluster, and whether they have
|
||||
// votes. This should include the local server, if it's a member of the cluster.
|
||||
// The servers are listed no particular order, but each should only appear once.
|
||||
// These entries are appended to the log during membership changes.
|
||||
type Configuration struct {
|
||||
Servers []Server
|
||||
}
|
||||
|
||||
// Clone makes a deep copy of a Configuration.
|
||||
func (c *Configuration) Clone() (copy Configuration) {
|
||||
copy.Servers = append(copy.Servers, c.Servers...)
|
||||
return
|
||||
}
|
||||
|
||||
// ConfigurationChangeCommand is the different ways to change the cluster
|
||||
// configuration.
|
||||
type ConfigurationChangeCommand uint8
|
||||
|
||||
const (
|
||||
// AddStaging makes a server Staging unless its Voter.
|
||||
AddStaging ConfigurationChangeCommand = iota
|
||||
// AddNonvoter makes a server Nonvoter unless its Staging or Voter.
|
||||
AddNonvoter
|
||||
// DemoteVoter makes a server Nonvoter unless its absent.
|
||||
DemoteVoter
|
||||
// RemoveServer removes a server entirely from the cluster membership.
|
||||
RemoveServer
|
||||
// Promote is created automatically by a leader; it turns a Staging server
|
||||
// into a Voter.
|
||||
Promote
|
||||
)
|
||||
|
||||
func (c ConfigurationChangeCommand) String() string {
|
||||
switch c {
|
||||
case AddStaging:
|
||||
return "AddStaging"
|
||||
case AddNonvoter:
|
||||
return "AddNonvoter"
|
||||
case DemoteVoter:
|
||||
return "DemoteVoter"
|
||||
case RemoveServer:
|
||||
return "RemoveServer"
|
||||
case Promote:
|
||||
return "Promote"
|
||||
}
|
||||
return "ConfigurationChangeCommand"
|
||||
}
|
||||
|
||||
// configurationChangeRequest describes a change that a leader would like to
|
||||
// make to its current configuration. It's used only within a single server
|
||||
// (never serialized into the log), as part of `configurationChangeFuture`.
|
||||
type configurationChangeRequest struct {
|
||||
command ConfigurationChangeCommand
|
||||
serverID ServerID
|
||||
serverAddress ServerAddress // only present for AddStaging, AddNonvoter
|
||||
// prevIndex, if nonzero, is the index of the only configuration upon which
|
||||
// this change may be applied; if another configuration entry has been
|
||||
// added in the meantime, this request will fail.
|
||||
prevIndex uint64
|
||||
}
|
||||
|
||||
// configurations is state tracked on every server about its Configurations.
|
||||
// Note that, per Diego's dissertation, there can be at most one uncommitted
|
||||
// configuration at a time (the next configuration may not be created until the
|
||||
// prior one has been committed).
|
||||
//
|
||||
// One downside to storing just two configurations is that if you try to take a
|
||||
// snapshot when your state machine hasn't yet applied the committedIndex, we
|
||||
// have no record of the configuration that would logically fit into that
|
||||
// snapshot. We disallow snapshots in that case now. An alternative approach,
|
||||
// which LogCabin uses, is to track every configuration change in the
|
||||
// log.
|
||||
type configurations struct {
|
||||
// committed is the latest configuration in the log/snapshot that has been
|
||||
// committed (the one with the largest index).
|
||||
committed Configuration
|
||||
// committedIndex is the log index where 'committed' was written.
|
||||
committedIndex uint64
|
||||
// latest is the latest configuration in the log/snapshot (may be committed
|
||||
// or uncommitted)
|
||||
latest Configuration
|
||||
// latestIndex is the log index where 'latest' was written.
|
||||
latestIndex uint64
|
||||
}
|
||||
|
||||
// Clone makes a deep copy of a configurations object.
|
||||
func (c *configurations) Clone() (copy configurations) {
|
||||
copy.committed = c.committed.Clone()
|
||||
copy.committedIndex = c.committedIndex
|
||||
copy.latest = c.latest.Clone()
|
||||
copy.latestIndex = c.latestIndex
|
||||
return
|
||||
}
|
||||
|
||||
// hasVote returns true if the server identified by 'id' is a Voter in the
|
||||
// provided Configuration.
|
||||
func hasVote(configuration Configuration, id ServerID) bool {
|
||||
for _, server := range configuration.Servers {
|
||||
if server.ID == id {
|
||||
return server.Suffrage == Voter
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkConfiguration tests a cluster membership configuration for common
|
||||
// errors.
|
||||
func checkConfiguration(configuration Configuration) error {
|
||||
idSet := make(map[ServerID]bool)
|
||||
addressSet := make(map[ServerAddress]bool)
|
||||
var voters int
|
||||
for _, server := range configuration.Servers {
|
||||
if server.ID == "" {
|
||||
return fmt.Errorf("Empty ID in configuration: %v", configuration)
|
||||
}
|
||||
if server.Address == "" {
|
||||
return fmt.Errorf("Empty address in configuration: %v", server)
|
||||
}
|
||||
if idSet[server.ID] {
|
||||
return fmt.Errorf("Found duplicate ID in configuration: %v", server.ID)
|
||||
}
|
||||
idSet[server.ID] = true
|
||||
if addressSet[server.Address] {
|
||||
return fmt.Errorf("Found duplicate address in configuration: %v", server.Address)
|
||||
}
|
||||
addressSet[server.Address] = true
|
||||
if server.Suffrage == Voter {
|
||||
voters++
|
||||
}
|
||||
}
|
||||
if voters == 0 {
|
||||
return fmt.Errorf("Need at least one voter in configuration: %v", configuration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextConfiguration generates a new Configuration from the current one and a
|
||||
// configuration change request. It's split from appendConfigurationEntry so
|
||||
// that it can be unit tested easily.
|
||||
func nextConfiguration(current Configuration, currentIndex uint64, change configurationChangeRequest) (Configuration, error) {
|
||||
if change.prevIndex > 0 && change.prevIndex != currentIndex {
|
||||
return Configuration{}, fmt.Errorf("Configuration changed since %v (latest is %v)", change.prevIndex, currentIndex)
|
||||
}
|
||||
|
||||
configuration := current.Clone()
|
||||
switch change.command {
|
||||
case AddStaging:
|
||||
// TODO: barf on new address?
|
||||
newServer := Server{
|
||||
// TODO: This should add the server as Staging, to be automatically
|
||||
// promoted to Voter later. However, the promotion to Voter is not yet
|
||||
// implemented, and doing so is not trivial with the way the leader loop
|
||||
// coordinates with the replication goroutines today. So, for now, the
|
||||
// server will have a vote right away, and the Promote case below is
|
||||
// unused.
|
||||
Suffrage: Voter,
|
||||
ID: change.serverID,
|
||||
Address: change.serverAddress,
|
||||
}
|
||||
found := false
|
||||
for i, server := range configuration.Servers {
|
||||
if server.ID == change.serverID {
|
||||
if server.Suffrage == Voter {
|
||||
configuration.Servers[i].Address = change.serverAddress
|
||||
} else {
|
||||
configuration.Servers[i] = newServer
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
configuration.Servers = append(configuration.Servers, newServer)
|
||||
}
|
||||
case AddNonvoter:
|
||||
newServer := Server{
|
||||
Suffrage: Nonvoter,
|
||||
ID: change.serverID,
|
||||
Address: change.serverAddress,
|
||||
}
|
||||
found := false
|
||||
for i, server := range configuration.Servers {
|
||||
if server.ID == change.serverID {
|
||||
if server.Suffrage != Nonvoter {
|
||||
configuration.Servers[i].Address = change.serverAddress
|
||||
} else {
|
||||
configuration.Servers[i] = newServer
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
configuration.Servers = append(configuration.Servers, newServer)
|
||||
}
|
||||
case DemoteVoter:
|
||||
for i, server := range configuration.Servers {
|
||||
if server.ID == change.serverID {
|
||||
configuration.Servers[i].Suffrage = Nonvoter
|
||||
break
|
||||
}
|
||||
}
|
||||
case RemoveServer:
|
||||
for i, server := range configuration.Servers {
|
||||
if server.ID == change.serverID {
|
||||
configuration.Servers = append(configuration.Servers[:i], configuration.Servers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
case Promote:
|
||||
for i, server := range configuration.Servers {
|
||||
if server.ID == change.serverID && server.Suffrage == Staging {
|
||||
configuration.Servers[i].Suffrage = Voter
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we didn't do something bad like remove the last voter
|
||||
if err := checkConfiguration(configuration); err != nil {
|
||||
return Configuration{}, err
|
||||
}
|
||||
|
||||
return configuration, nil
|
||||
}
|
||||
|
||||
// encodePeers is used to serialize a Configuration into the old peers format.
|
||||
// This is here for backwards compatibility when operating with a mix of old
|
||||
// servers and should be removed once we deprecate support for protocol version 1.
|
||||
func encodePeers(configuration Configuration, trans Transport) []byte {
|
||||
// Gather up all the voters, other suffrage types are not supported by
|
||||
// this data format.
|
||||
var encPeers [][]byte
|
||||
for _, server := range configuration.Servers {
|
||||
if server.Suffrage == Voter {
|
||||
encPeers = append(encPeers, trans.EncodePeer(server.ID, server.Address))
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the entire array.
|
||||
buf, err := encodeMsgPack(encPeers)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to encode peers: %v", err))
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// decodePeers is used to deserialize an old list of peers into a Configuration.
|
||||
// This is here for backwards compatibility with old log entries and snapshots;
|
||||
// it should be removed eventually.
|
||||
func decodePeers(buf []byte, trans Transport) Configuration {
|
||||
// Decode the buffer first.
|
||||
var encPeers [][]byte
|
||||
if err := decodeMsgPack(buf, &encPeers); err != nil {
|
||||
panic(fmt.Errorf("failed to decode peers: %v", err))
|
||||
}
|
||||
|
||||
// Deserialize each peer.
|
||||
var servers []Server
|
||||
for _, enc := range encPeers {
|
||||
p := trans.DecodePeer(enc)
|
||||
servers = append(servers, Server{
|
||||
Suffrage: Voter,
|
||||
ID: ServerID(p),
|
||||
Address: ServerAddress(p),
|
||||
})
|
||||
}
|
||||
|
||||
return Configuration{
|
||||
Servers: servers,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeConfiguration serializes a Configuration using MsgPack, or panics on
|
||||
// errors.
|
||||
func encodeConfiguration(configuration Configuration) []byte {
|
||||
buf, err := encodeMsgPack(configuration)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to encode configuration: %v", err))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// decodeConfiguration deserializes a Configuration using MsgPack, or panics on
|
||||
// errors.
|
||||
func decodeConfiguration(buf []byte) Configuration {
|
||||
var configuration Configuration
|
||||
if err := decodeMsgPack(buf, &configuration); err != nil {
|
||||
panic(fmt.Errorf("failed to decode configuration: %v", err))
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
49
vendor/github.com/hashicorp/raft/discard_snapshot.go
generated
vendored
Normal file
49
vendor/github.com/hashicorp/raft/discard_snapshot.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// DiscardSnapshotStore is used to successfully snapshot while
|
||||
// always discarding the snapshot. This is useful for when the
|
||||
// log should be truncated but no snapshot should be retained.
|
||||
// This should never be used for production use, and is only
|
||||
// suitable for testing.
|
||||
type DiscardSnapshotStore struct{}
|
||||
|
||||
type DiscardSnapshotSink struct{}
|
||||
|
||||
// NewDiscardSnapshotStore is used to create a new DiscardSnapshotStore.
|
||||
func NewDiscardSnapshotStore() *DiscardSnapshotStore {
|
||||
return &DiscardSnapshotStore{}
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotStore) Create(version SnapshotVersion, index, term uint64,
|
||||
configuration Configuration, configurationIndex uint64, trans Transport) (SnapshotSink, error) {
|
||||
return &DiscardSnapshotSink{}, nil
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotStore) List() ([]*SnapshotMeta, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotStore) Open(id string) (*SnapshotMeta, io.ReadCloser, error) {
|
||||
return nil, nil, fmt.Errorf("open is not supported")
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotSink) Write(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotSink) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotSink) ID() string {
|
||||
return "discard"
|
||||
}
|
||||
|
||||
func (d *DiscardSnapshotSink) Cancel() error {
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue