mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 21:35:52 +00:00
6c8ab1bbac
1. Fix a bug where the peering leader routine would not track all active peerings in the "stored" reconciliation map. This could lead to tearing down streams where the token was generated, since the ConnectedStreams() method used for reconciliation returns all streams and not just the ones initiated by this leader routine. 2. Fix a race where stream contexts were being canceled before termination messages were being processed by a peer. Previously the leader routine would tear down streams by canceling their context right after the termination message was sent. This context cancelation could be propagated to the server side faster than the termination message. Now there is a change where the dialing peer uses CloseSend() to signal when no more messages will be sent. Eventually the server peer will read an EOF after receiving and processing the preceding termination message. Using CloseSend() is actually not enough to address the issue mentioned, since it doesn't wait for the server peer to finish processing messages. Because of this now the dialing peer also reads from the stream until an error signals that there are no more messages. Receiving an EOF from our peer indicates that they processed the termination message and have no additional work to do. Given that the stream is being closed, all the messages received by Recv are discarded. We only check for errors to avoid importing new data.
284 lines
7.5 KiB
Go
284 lines
7.5 KiB
Go
package pbpeering
|
|
|
|
import (
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/ptypes/timestamp"
|
|
"github.com/mitchellh/hashstructure"
|
|
|
|
"github.com/hashicorp/consul/agent/cache"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/api"
|
|
"github.com/hashicorp/consul/lib"
|
|
)
|
|
|
|
// TODO(peering): These are byproducts of not embedding
|
|
// types in our protobuf definitions and are temporary;
|
|
// Hoping to replace them with 1 or 2 methods per request
|
|
// using https://github.com/hashicorp/consul/pull/12507
|
|
|
|
// RequestDatacenter implements structs.RPCInfo
|
|
func (req *GenerateTokenRequest) RequestDatacenter() string {
|
|
return req.Datacenter
|
|
}
|
|
|
|
// IsRead implements structs.RPCInfo
|
|
func (req *GenerateTokenRequest) IsRead() bool {
|
|
return false
|
|
}
|
|
|
|
// AllowStaleRead implements structs.RPCInfo
|
|
func (req *GenerateTokenRequest) AllowStaleRead() bool {
|
|
return false
|
|
}
|
|
|
|
// TokenSecret implements structs.RPCInfo
|
|
func (req *GenerateTokenRequest) TokenSecret() string {
|
|
return req.Token
|
|
}
|
|
|
|
// SetTokenSecret implements structs.RPCInfo
|
|
func (req *GenerateTokenRequest) SetTokenSecret(token string) {
|
|
req.Token = token
|
|
}
|
|
|
|
// HasTimedOut implements structs.RPCInfo
|
|
func (req *GenerateTokenRequest) HasTimedOut(start time.Time, rpcHoldTimeout, _, _ time.Duration) (bool, error) {
|
|
return time.Since(start) > rpcHoldTimeout, nil
|
|
}
|
|
|
|
// Timeout implements structs.RPCInfo
|
|
func (msg *GenerateTokenRequest) Timeout(rpcHoldTimeout time.Duration, maxQueryTime time.Duration, defaultQueryTime time.Duration) time.Duration {
|
|
return rpcHoldTimeout
|
|
}
|
|
|
|
// IsRead implements structs.RPCInfo
|
|
func (req *EstablishRequest) IsRead() bool {
|
|
return false
|
|
}
|
|
|
|
// AllowStaleRead implements structs.RPCInfo
|
|
func (req *EstablishRequest) AllowStaleRead() bool {
|
|
return false
|
|
}
|
|
|
|
// TokenSecret implements structs.RPCInfo
|
|
func (req *EstablishRequest) TokenSecret() string {
|
|
return req.Token
|
|
}
|
|
|
|
// SetTokenSecret implements structs.RPCInfo
|
|
func (req *EstablishRequest) SetTokenSecret(token string) {
|
|
req.Token = token
|
|
}
|
|
|
|
// HasTimedOut implements structs.RPCInfo
|
|
func (req *EstablishRequest) HasTimedOut(start time.Time, rpcHoldTimeout, _, _ time.Duration) (bool, error) {
|
|
return time.Since(start) > rpcHoldTimeout, nil
|
|
}
|
|
|
|
// Timeout implements structs.RPCInfo
|
|
func (msg *EstablishRequest) Timeout(rpcHoldTimeout time.Duration, maxQueryTime time.Duration, defaultQueryTime time.Duration) time.Duration {
|
|
return rpcHoldTimeout
|
|
}
|
|
|
|
// ShouldDial returns true when the peering was stored via the peering initiation endpoint,
|
|
// AND the peering is not marked as terminated by our peer.
|
|
// If we generated a token for this peer we did not store our server addresses under PeerServerAddresses.
|
|
// These server addresses are for dialing, and only the peer initiating the peering will do the dialing.
|
|
func (p *Peering) ShouldDial() bool {
|
|
return len(p.PeerServerAddresses) > 0
|
|
}
|
|
|
|
func (x ReplicationMessage_Response_Operation) GoString() string {
|
|
return x.String()
|
|
}
|
|
|
|
func (x PeeringState) GoString() string {
|
|
return x.String()
|
|
}
|
|
|
|
func (r *TrustBundleReadRequest) CacheInfo() cache.RequestInfo {
|
|
info := cache.RequestInfo{
|
|
// TODO(peering): Revisit whether this is the token to use once request types accept a token.
|
|
Token: r.Token(),
|
|
Datacenter: r.Datacenter,
|
|
MinIndex: 0,
|
|
Timeout: 0,
|
|
MustRevalidate: false,
|
|
|
|
// TODO(peering): Cache.notifyPollingQuery polls at this interval. We need to revisit how that polling works.
|
|
// Using an exponential backoff when the result hasn't changed may be preferable.
|
|
MaxAge: 1 * time.Second,
|
|
}
|
|
|
|
v, err := hashstructure.Hash([]interface{}{
|
|
r.Partition,
|
|
r.Name,
|
|
}, nil)
|
|
if err == nil {
|
|
// If there is an error, we don't set the key. A blank key forces
|
|
// no cache for this request so the request is forwarded directly
|
|
// to the server.
|
|
info.Key = strconv.FormatUint(v, 10)
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// ConcatenatedRootPEMs concatenates and returns all PEM-encoded public certificates
|
|
// in a peer's trust bundle.
|
|
func (b *PeeringTrustBundle) ConcatenatedRootPEMs() string {
|
|
if b == nil {
|
|
return ""
|
|
}
|
|
|
|
var rootPEMs string
|
|
for _, pem := range b.RootPEMs {
|
|
rootPEMs += lib.EnsureTrailingNewline(pem)
|
|
}
|
|
return rootPEMs
|
|
}
|
|
|
|
// enumcover:PeeringState
|
|
func PeeringStateToAPI(s PeeringState) api.PeeringState {
|
|
switch s {
|
|
case PeeringState_INITIAL:
|
|
return api.PeeringStateInitial
|
|
case PeeringState_ACTIVE:
|
|
return api.PeeringStateActive
|
|
case PeeringState_FAILING:
|
|
return api.PeeringStateFailing
|
|
case PeeringState_TERMINATED:
|
|
return api.PeeringStateTerminated
|
|
case PeeringState_UNDEFINED:
|
|
fallthrough
|
|
default:
|
|
return api.PeeringStateUndefined
|
|
}
|
|
}
|
|
|
|
// enumcover:api.PeeringState
|
|
func PeeringStateFromAPI(t api.PeeringState) PeeringState {
|
|
switch t {
|
|
case api.PeeringStateInitial:
|
|
return PeeringState_INITIAL
|
|
case api.PeeringStateActive:
|
|
return PeeringState_ACTIVE
|
|
case api.PeeringStateFailing:
|
|
return PeeringState_FAILING
|
|
case api.PeeringStateTerminated:
|
|
return PeeringState_TERMINATED
|
|
case api.PeeringStateUndefined:
|
|
fallthrough
|
|
default:
|
|
return PeeringState_UNDEFINED
|
|
}
|
|
}
|
|
|
|
func (p *Peering) IsActive() bool {
|
|
if p != nil && p.State == PeeringState_TERMINATED {
|
|
return false
|
|
}
|
|
if p == nil || p.DeletedAt == nil {
|
|
return true
|
|
}
|
|
|
|
// The minimum protobuf timestamp is the Unix epoch rather than go's zero.
|
|
return structs.IsZeroProtoTime(p.DeletedAt)
|
|
}
|
|
|
|
func (p *Peering) ToAPI() *api.Peering {
|
|
var t api.Peering
|
|
PeeringToAPI(p, &t)
|
|
return &t
|
|
}
|
|
|
|
// TODO consider using mog for this
|
|
func (resp *PeeringListResponse) ToAPI() []*api.Peering {
|
|
list := make([]*api.Peering, len(resp.Peerings))
|
|
for i, p := range resp.Peerings {
|
|
list[i] = p.ToAPI()
|
|
}
|
|
return list
|
|
}
|
|
|
|
// TODO consider using mog for this
|
|
func (resp *GenerateTokenResponse) ToAPI() *api.PeeringGenerateTokenResponse {
|
|
var t api.PeeringGenerateTokenResponse
|
|
GenerateTokenResponseToAPI(resp, &t)
|
|
return &t
|
|
}
|
|
|
|
// TODO consider using mog for this
|
|
func (resp *EstablishResponse) ToAPI() *api.PeeringEstablishResponse {
|
|
var t api.PeeringEstablishResponse
|
|
EstablishResponseToAPI(resp, &t)
|
|
return &t
|
|
}
|
|
|
|
// convenience
|
|
func NewGenerateTokenRequestFromAPI(req *api.PeeringGenerateTokenRequest) *GenerateTokenRequest {
|
|
if req == nil {
|
|
return nil
|
|
}
|
|
t := &GenerateTokenRequest{}
|
|
GenerateTokenRequestFromAPI(req, t)
|
|
return t
|
|
}
|
|
|
|
// convenience
|
|
func NewEstablishRequestFromAPI(req *api.PeeringEstablishRequest) *EstablishRequest {
|
|
if req == nil {
|
|
return nil
|
|
}
|
|
t := &EstablishRequest{}
|
|
EstablishRequestFromAPI(req, t)
|
|
return t
|
|
}
|
|
|
|
func (r *TrustBundleListByServiceRequest) CacheInfo() cache.RequestInfo {
|
|
info := cache.RequestInfo{
|
|
// TODO(peering): Revisit whether this is the token to use once request types accept a token.
|
|
Token: r.Token(),
|
|
Datacenter: r.Datacenter,
|
|
MinIndex: 0,
|
|
Timeout: 0,
|
|
MustRevalidate: false,
|
|
|
|
// TODO(peering): Cache.notifyPollingQuery polls at this interval. We need to revisit how that polling works.
|
|
// Using an exponential backoff when the result hasn't changed may be preferable.
|
|
MaxAge: 1 * time.Second,
|
|
}
|
|
|
|
v, err := hashstructure.Hash([]interface{}{
|
|
r.Partition,
|
|
r.Namespace,
|
|
r.ServiceName,
|
|
}, nil)
|
|
if err == nil {
|
|
// If there is an error, we don't set the key. A blank key forces
|
|
// no cache for this request so the request is forwarded directly
|
|
// to the server.
|
|
info.Key = strconv.FormatUint(v, 10)
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
func TimePtrFromProto(s *timestamp.Timestamp) *time.Time {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
t := structs.TimeFromProto(s)
|
|
return &t
|
|
}
|
|
|
|
func TimePtrToProto(s *time.Time) *timestamp.Timestamp {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
return structs.TimeToProto(*s)
|
|
}
|