2020-10-01 06:21:50 +00:00
|
|
|
package submatview
|
2020-09-18 22:25:56 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-hclog"
|
2020-09-18 22:33:02 +00:00
|
|
|
|
2020-09-29 21:42:48 +00:00
|
|
|
"github.com/hashicorp/consul/lib/retry"
|
2020-09-18 22:33:02 +00:00
|
|
|
"github.com/hashicorp/consul/proto/pbsubscribe"
|
2020-09-18 22:25:56 +00:00
|
|
|
)
|
|
|
|
|
2020-10-01 06:36:36 +00:00
|
|
|
// View receives events from, and return results to, Materializer. A view is
|
|
|
|
// responsible for converting the pbsubscribe.Event.Payload into the local
|
|
|
|
// type, and storing it so that it can be returned by Result().
|
2020-09-29 21:42:48 +00:00
|
|
|
type View interface {
|
2020-09-18 22:25:56 +00:00
|
|
|
// Update is called when one or more events are received. The first call will
|
|
|
|
// include _all_ events in the initial snapshot which may be an empty set.
|
|
|
|
// Subsequent calls will contain one or more update events in the order they
|
|
|
|
// are received.
|
2020-09-18 22:33:02 +00:00
|
|
|
Update(events []*pbsubscribe.Event) error
|
2020-09-18 22:25:56 +00:00
|
|
|
|
|
|
|
// Result returns the type-specific cache result based on the state. When no
|
|
|
|
// events have been delivered yet the result should be an empty value type
|
|
|
|
// suitable to return to clients in case there is an empty result on the
|
|
|
|
// servers. The index the materialized view represents is maintained
|
|
|
|
// separately and passed in in case the return type needs an Index field
|
|
|
|
// populating. This allows implementations to not worry about maintaining
|
|
|
|
// indexes seen during Update.
|
2021-02-23 19:27:24 +00:00
|
|
|
Result(index uint64) interface{}
|
2020-09-18 22:25:56 +00:00
|
|
|
|
2020-09-29 21:42:48 +00:00
|
|
|
// Reset the view to the zero state, done in preparation for receiving a new
|
|
|
|
// snapshot.
|
|
|
|
Reset()
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
// Result returned from the View.
|
|
|
|
type Result struct {
|
|
|
|
Index uint64
|
|
|
|
Value interface{}
|
|
|
|
// Cached is true if the requested value was already available locally. If
|
|
|
|
// the value is false, it indicates that GetFromView had to wait for an update,
|
|
|
|
Cached bool
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
2020-10-01 06:36:36 +00:00
|
|
|
type Deps struct {
|
|
|
|
View View
|
2020-09-29 21:42:48 +00:00
|
|
|
Logger hclog.Logger
|
|
|
|
Waiter *retry.Waiter
|
2022-03-30 16:51:56 +00:00
|
|
|
Request func(index uint64) *pbsubscribe.SubscribeRequest
|
2020-09-29 21:42:48 +00:00
|
|
|
}
|
|
|
|
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
// materializer consumes the event stream, handling any framing events, and
|
|
|
|
// allows for querying the materialized view.
|
|
|
|
type materializer struct {
|
|
|
|
retryWaiter *retry.Waiter
|
|
|
|
logger hclog.Logger
|
2020-09-18 22:25:56 +00:00
|
|
|
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
// lock protects the mutable state - all fields below it must only be accessed
|
|
|
|
// while holding lock.
|
|
|
|
lock sync.Mutex
|
|
|
|
index uint64
|
|
|
|
view View
|
|
|
|
updateCh chan struct{}
|
|
|
|
err error
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
func newMaterializer(logger hclog.Logger, view View, waiter *retry.Waiter) *materializer {
|
|
|
|
m := materializer{
|
|
|
|
view: view,
|
|
|
|
retryWaiter: waiter,
|
|
|
|
logger: logger,
|
|
|
|
updateCh: make(chan struct{}),
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
if m.retryWaiter == nil {
|
|
|
|
m.retryWaiter = defaultWaiter()
|
2020-10-01 06:36:36 +00:00
|
|
|
}
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
return &m
|
2021-02-23 19:27:24 +00:00
|
|
|
}
|
2020-09-18 22:25:56 +00:00
|
|
|
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
// Query blocks until the index of the View is greater than opts.MinIndex,
|
|
|
|
// or the context is cancelled.
|
|
|
|
func (m *materializer) query(ctx context.Context, minIndex uint64) (Result, error) {
|
2020-10-01 06:36:36 +00:00
|
|
|
m.lock.Lock()
|
2020-09-18 22:25:56 +00:00
|
|
|
|
2021-02-23 20:02:28 +00:00
|
|
|
result := Result{
|
2021-02-23 19:27:24 +00:00
|
|
|
Index: m.index,
|
|
|
|
Value: m.view.Result(m.index),
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 19:27:24 +00:00
|
|
|
updateCh := m.updateCh
|
|
|
|
m.lock.Unlock()
|
2020-09-18 22:25:56 +00:00
|
|
|
|
|
|
|
// If our index is > req.Index return right away. If index is zero then we
|
|
|
|
// haven't loaded a snapshot at all yet which means we should wait for one on
|
2021-04-27 22:15:57 +00:00
|
|
|
// the update chan.
|
2021-02-23 19:27:24 +00:00
|
|
|
if result.Index > 0 && result.Index > minIndex {
|
2021-06-28 21:29:23 +00:00
|
|
|
result.Cached = true
|
2020-09-18 22:25:56 +00:00
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-updateCh:
|
|
|
|
// View updated, return the new result
|
2020-10-01 06:36:36 +00:00
|
|
|
m.lock.Lock()
|
|
|
|
result.Index = m.index
|
2020-09-18 22:25:56 +00:00
|
|
|
|
2021-04-27 22:15:57 +00:00
|
|
|
switch {
|
|
|
|
case m.err != nil:
|
2021-04-26 16:20:33 +00:00
|
|
|
err := m.err
|
2021-02-23 19:27:24 +00:00
|
|
|
m.lock.Unlock()
|
2021-04-26 16:20:33 +00:00
|
|
|
return result, err
|
2021-04-27 22:15:57 +00:00
|
|
|
case result.Index <= minIndex:
|
|
|
|
// get a reference to the new updateCh, the previous one was closed
|
|
|
|
updateCh = m.updateCh
|
|
|
|
m.lock.Unlock()
|
|
|
|
continue
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 19:27:24 +00:00
|
|
|
result.Value = m.view.Result(m.index)
|
|
|
|
m.lock.Unlock()
|
2020-09-18 22:25:56 +00:00
|
|
|
return result, nil
|
|
|
|
|
2021-02-23 19:27:24 +00:00
|
|
|
case <-ctx.Done():
|
2021-04-27 22:15:57 +00:00
|
|
|
// Update the result value to the latest because callers may still
|
|
|
|
// use the value when the error is context.DeadlineExceeded
|
|
|
|
m.lock.Lock()
|
|
|
|
result.Value = m.view.Result(m.index)
|
|
|
|
m.lock.Unlock()
|
2021-02-23 19:27:24 +00:00
|
|
|
return result, ctx.Err()
|
2020-09-18 22:25:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
|
|
|
|
func (m *materializer) currentIndex() uint64 {
|
|
|
|
var resp uint64
|
|
|
|
|
|
|
|
m.lock.Lock()
|
|
|
|
resp = m.index
|
|
|
|
m.lock.Unlock()
|
|
|
|
|
|
|
|
return resp
|
|
|
|
}
|
|
|
|
|
|
|
|
// notifyUpdateLocked closes the current update channel and recreates a new
|
|
|
|
// one. It must be called while holding the m.lock lock.
|
|
|
|
func (m *materializer) notifyUpdateLocked(err error) {
|
|
|
|
m.err = err
|
|
|
|
close(m.updateCh)
|
|
|
|
m.updateCh = make(chan struct{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset clears the state ready to start a new stream from scratch.
|
|
|
|
func (m *materializer) reset() {
|
|
|
|
m.lock.Lock()
|
|
|
|
defer m.lock.Unlock()
|
|
|
|
|
|
|
|
m.view.Reset()
|
|
|
|
m.index = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateView updates the view from a sequence of events and stores
|
|
|
|
// the corresponding Raft index.
|
|
|
|
func (m *materializer) updateView(events []*pbsubscribe.Event, index uint64) error {
|
|
|
|
m.lock.Lock()
|
|
|
|
defer m.lock.Unlock()
|
|
|
|
|
|
|
|
if err := m.view.Update(events); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.index = index
|
|
|
|
m.notifyUpdateLocked(nil)
|
|
|
|
m.retryWaiter.Reset()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *materializer) handleError(req *pbsubscribe.SubscribeRequest, err error) {
|
|
|
|
failures := m.retryWaiter.Failures()
|
|
|
|
if isNonTemporaryOrConsecutiveFailure(err, failures) {
|
|
|
|
m.lock.Lock()
|
|
|
|
m.notifyUpdateLocked(err)
|
|
|
|
m.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
m.logger.Error("subscribe call failed",
|
|
|
|
"err", err,
|
|
|
|
"topic", req.Topic,
|
|
|
|
"key", req.Key,
|
|
|
|
"failure_count", failures+1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// isNonTemporaryOrConsecutiveFailure returns true if the error is not a
|
|
|
|
// temporary error or if failures > 0.
|
|
|
|
func isNonTemporaryOrConsecutiveFailure(err error, failures int) bool {
|
|
|
|
// temporary is an interface used by net and other std lib packages to
|
|
|
|
// show error types represent temporary/recoverable errors.
|
|
|
|
temp, ok := err.(interface {
|
|
|
|
Temporary() bool
|
|
|
|
})
|
|
|
|
return !ok || !temp.Temporary() || failures > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultWaiter() *retry.Waiter {
|
|
|
|
return &retry.Waiter{
|
|
|
|
MinFailures: 1,
|
|
|
|
// Start backing off with small increments (200-400ms) which will double
|
|
|
|
// each attempt. (200-400, 400-800, 800-1600, 1600-3200, 3200-6000, 6000
|
|
|
|
// after that). (retry.Wait applies Max limit after jitter right now).
|
|
|
|
Factor: 200 * time.Millisecond,
|
|
|
|
MinWait: 0,
|
|
|
|
MaxWait: 60 * time.Second,
|
|
|
|
Jitter: retry.NewJitter(100),
|
|
|
|
}
|
|
|
|
}
|