mirror of https://github.com/hashicorp/consul
169 lines
5.0 KiB
Go
169 lines
5.0 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package peerstream
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/acl/resolver"
|
|
"github.com/hashicorp/consul/agent/cache"
|
|
"github.com/hashicorp/consul/agent/consul/stream"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/agent/submatview"
|
|
"github.com/hashicorp/consul/proto/private/pbservice"
|
|
"github.com/hashicorp/consul/proto/private/pbsubscribe"
|
|
)
|
|
|
|
type Subscriber interface {
|
|
Subscribe(req *stream.SubscribeRequest) (*stream.Subscription, error)
|
|
}
|
|
|
|
type exportedServiceRequest struct {
|
|
logger hclog.Logger
|
|
req structs.ServiceSpecificRequest
|
|
sub Subscriber
|
|
}
|
|
|
|
func newExportedStandardServiceRequest(logger hclog.Logger, svc structs.ServiceName, sub Subscriber) *exportedServiceRequest {
|
|
req := structs.ServiceSpecificRequest{
|
|
ServiceName: svc.Name,
|
|
Connect: false,
|
|
EnterpriseMeta: svc.EnterpriseMeta,
|
|
}
|
|
return &exportedServiceRequest{
|
|
logger: logger,
|
|
req: req,
|
|
sub: sub,
|
|
}
|
|
}
|
|
|
|
// CacheInfo implements submatview.Request
|
|
func (e *exportedServiceRequest) CacheInfo() cache.RequestInfo {
|
|
return e.req.CacheInfo()
|
|
}
|
|
|
|
func (e *exportedServiceRequest) getTopic() pbsubscribe.Topic {
|
|
if e.req.Connect {
|
|
return pbsubscribe.Topic_ServiceHealthConnect
|
|
}
|
|
// Using the Topic_ServiceHealth will ignore proxies unless the ServiceName is a proxy name.
|
|
return pbsubscribe.Topic_ServiceHealth
|
|
}
|
|
|
|
// NewMaterializer implements submatview.Request
|
|
func (e *exportedServiceRequest) NewMaterializer() (submatview.Materializer, error) {
|
|
// TODO(peering): reinstate this
|
|
// if e.req.Connect {
|
|
// return nil, fmt.Errorf("connect views are not supported")
|
|
// }
|
|
reqFn := func(index uint64) *pbsubscribe.SubscribeRequest {
|
|
return &pbsubscribe.SubscribeRequest{
|
|
Topic: e.getTopic(),
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
Key: e.req.ServiceName,
|
|
Namespace: e.req.EnterpriseMeta.NamespaceOrEmpty(),
|
|
Partition: e.req.EnterpriseMeta.PartitionOrEmpty(),
|
|
},
|
|
},
|
|
Token: e.req.Token,
|
|
Datacenter: e.req.Datacenter,
|
|
Index: index,
|
|
}
|
|
}
|
|
deps := submatview.LocalMaterializerDeps{
|
|
Backend: e.sub,
|
|
ACLResolver: DANGER_NO_AUTH{},
|
|
Deps: submatview.Deps{
|
|
View: newExportedServicesView(),
|
|
Logger: e.logger,
|
|
Request: reqFn,
|
|
},
|
|
}
|
|
return submatview.NewLocalMaterializer(deps), nil
|
|
}
|
|
|
|
// DANGER_NO_AUTH implements submatview.ACLResolver to short-circuit authorization
|
|
// in cases where it is handled somewhere else (e.g. in an RPC handler).
|
|
type DANGER_NO_AUTH struct{}
|
|
|
|
func (DANGER_NO_AUTH) ResolveTokenAndDefaultMeta(string, *acl.EnterpriseMeta, *acl.AuthorizerContext) (resolver.Result, error) {
|
|
return resolver.Result{Authorizer: acl.ManageAll()}, nil
|
|
}
|
|
|
|
// Type implements submatview.Request
|
|
func (e *exportedServiceRequest) Type() string {
|
|
return "leader.peering.stream.exportedServiceRequest"
|
|
}
|
|
|
|
// exportedServicesView implements submatview.View for storing the view state
|
|
// of an exported service's health result. We store it as a map to make updates and
|
|
// deletions a little easier but we could just store a result type
|
|
// (IndexedCheckServiceNodes) and update it in place for each event - that
|
|
// involves re-sorting each time etc. though.
|
|
//
|
|
// Unlike rpcclient.healthView, there is no need for a filter because for exported services
|
|
// we export all instances unconditionally.
|
|
type exportedServicesView struct {
|
|
state map[string]*pbservice.CheckServiceNode
|
|
}
|
|
|
|
func newExportedServicesView() *exportedServicesView {
|
|
return &exportedServicesView{
|
|
state: make(map[string]*pbservice.CheckServiceNode),
|
|
}
|
|
}
|
|
|
|
// Reset implements submatview.View
|
|
func (s *exportedServicesView) Reset() {
|
|
s.state = make(map[string]*pbservice.CheckServiceNode)
|
|
}
|
|
|
|
// Update implements submatview.View
|
|
func (s *exportedServicesView) Update(events []*pbsubscribe.Event) error {
|
|
for _, event := range events {
|
|
serviceHealth := event.GetServiceHealth()
|
|
if serviceHealth == nil {
|
|
return fmt.Errorf("unexpected event type for service health view: %T",
|
|
event.GetPayload())
|
|
}
|
|
|
|
id := serviceHealth.CheckServiceNode.UniqueID()
|
|
switch serviceHealth.Op {
|
|
case pbsubscribe.CatalogOp_Register:
|
|
s.state[id] = serviceHealth.CheckServiceNode
|
|
|
|
case pbsubscribe.CatalogOp_Deregister:
|
|
delete(s.state, id)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Result returns the CheckServiceNodes stored by this view.
|
|
// Result implements submatview.View
|
|
func (s *exportedServicesView) Result(index uint64) interface{} {
|
|
result := pbservice.IndexedCheckServiceNodes{
|
|
Nodes: make([]*pbservice.CheckServiceNode, 0, len(s.state)),
|
|
Index: index,
|
|
}
|
|
for _, node := range s.state {
|
|
result.Nodes = append(result.Nodes, node)
|
|
}
|
|
sortCheckServiceNodes(&result)
|
|
|
|
return &result
|
|
}
|
|
|
|
// sortCheckServiceNodes stable sorts the results to match memdb semantics.
|
|
func sortCheckServiceNodes(n *pbservice.IndexedCheckServiceNodes) {
|
|
sort.SliceStable(n.Nodes, func(i, j int) bool {
|
|
return n.Nodes[i].UniqueID() < n.Nodes[j].UniqueID()
|
|
})
|
|
}
|