73897656d5
* Add v1/internal/service-virtual-ip for manually setting service VIPs * Attach service virtual IP info to compiled discovery chain * Separate auto-assigned and manual VIPs in response
823 lines
21 KiB
Go
823 lines
21 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-memdb"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
)
|
|
|
|
const (
|
|
tableNodes = "nodes"
|
|
tableServices = "services"
|
|
tableChecks = "checks"
|
|
tableGatewayServices = "gateway-services"
|
|
tableMeshTopology = "mesh-topology"
|
|
tableServiceVirtualIPs = "service-virtual-ips"
|
|
tableFreeVirtualIPs = "free-virtual-ips"
|
|
tableKindServiceNames = "kind-service-names"
|
|
|
|
indexID = "id"
|
|
indexService = "service"
|
|
indexConnect = "connect"
|
|
indexKind = "kind"
|
|
indexKindOnly = "kind-only"
|
|
indexStatus = "status"
|
|
indexNodeService = "node_service"
|
|
indexNode = "node"
|
|
indexUpstream = "upstream"
|
|
indexDownstream = "downstream"
|
|
indexGateway = "gateway"
|
|
indexUUID = "uuid"
|
|
indexMeta = "meta"
|
|
indexCounterOnly = "counter"
|
|
indexManualVIPs = "manual-vips"
|
|
)
|
|
|
|
// nodesTableSchema returns a new table schema used for storing struct.Node.
|
|
func nodesTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableNodes,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: indexerSingleWithPrefix[Query, *structs.Node, any]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexFromNode),
|
|
prefixIndex: prefixIndexFromQueryWithPeer,
|
|
},
|
|
},
|
|
indexUUID: {
|
|
Name: indexUUID,
|
|
AllowMissing: true,
|
|
Unique: true,
|
|
Indexer: indexerSingleWithPrefix[Query, *structs.Node, Query]{
|
|
readIndex: indexWithPeerName(indexFromUUIDQuery),
|
|
writeIndex: indexWithPeerName(indexIDFromNode),
|
|
prefixIndex: prefixIndexFromUUIDWithPeerQuery,
|
|
},
|
|
},
|
|
indexMeta: {
|
|
Name: indexMeta,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexerMulti[KeyValueQuery, *structs.Node]{
|
|
readIndex: indexWithPeerName(indexFromKeyValueQuery),
|
|
writeIndexMulti: multiIndexWithPeerName(indexMetaFromNode),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func indexFromNode(n *structs.Node) ([]byte, error) {
|
|
if n.Node == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(n.Node))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexIDFromNode(n *structs.Node) ([]byte, error) {
|
|
if n.ID == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
v, err := uuidStringToBytes(string(n.ID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
func indexMetaFromNode(n *structs.Node) ([][]byte, error) {
|
|
// NOTE: this is case-sensitive!
|
|
|
|
vals := make([][]byte, 0, len(n.Meta))
|
|
for key, val := range n.Meta {
|
|
if key == "" {
|
|
continue
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(key)
|
|
b.String(val)
|
|
vals = append(vals, b.Bytes())
|
|
}
|
|
if len(vals) == 0 {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
return vals, nil
|
|
}
|
|
|
|
// servicesTableSchema returns a new table schema used to store information
|
|
// about services.
|
|
func servicesTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableServices,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: indexerSingleWithPrefix[NodeServiceQuery, *structs.ServiceNode, any]{
|
|
readIndex: indexWithPeerName(indexFromNodeServiceQuery),
|
|
writeIndex: indexWithPeerName(indexFromServiceNode),
|
|
prefixIndex: prefixIndexFromQueryWithPeer,
|
|
},
|
|
},
|
|
indexNode: {
|
|
Name: indexNode,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, nodeIdentifier]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexFromNodeIdentity),
|
|
},
|
|
},
|
|
indexService: {
|
|
Name: indexService,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, *structs.ServiceNode]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexServiceNameFromServiceNode),
|
|
},
|
|
},
|
|
indexConnect: {
|
|
Name: indexConnect,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, *structs.ServiceNode]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexConnectNameFromServiceNode),
|
|
},
|
|
},
|
|
indexKind: {
|
|
Name: indexKind,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, *structs.ServiceNode]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexKindFromServiceNode),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func indexFromNodeServiceQuery(q NodeServiceQuery) ([]byte, error) {
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(q.Node))
|
|
b.String(strings.ToLower(q.Service))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexFromServiceNode(n *structs.ServiceNode) ([]byte, error) {
|
|
if n.Node == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(n.Node))
|
|
b.String(strings.ToLower(n.ServiceID))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
type nodeIdentifier interface {
|
|
partitionIndexable
|
|
peerIndexable
|
|
|
|
NodeIdentity() structs.Identity
|
|
}
|
|
|
|
var _ nodeIdentifier = (*structs.HealthCheck)(nil)
|
|
var _ nodeIdentifier = (*structs.ServiceNode)(nil)
|
|
|
|
func indexFromNodeIdentity(n nodeIdentifier) ([]byte, error) {
|
|
id := n.NodeIdentity()
|
|
if id.ID == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(id.ID))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexServiceNameFromServiceNode(n *structs.ServiceNode) ([]byte, error) {
|
|
if n.Node == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(n.ServiceName))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexConnectNameFromServiceNode(n *structs.ServiceNode) ([]byte, error) {
|
|
name, ok := connectNameFromServiceNode(n)
|
|
if !ok {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(name))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func connectNameFromServiceNode(sn *structs.ServiceNode) (string, bool) {
|
|
switch {
|
|
case sn.ServiceKind == structs.ServiceKindConnectProxy:
|
|
// For proxies, this service supports Connect for the destination
|
|
return sn.ServiceProxy.DestinationServiceName, true
|
|
|
|
case sn.ServiceConnect.Native:
|
|
// For native, this service supports Connect directly
|
|
return sn.ServiceName, true
|
|
|
|
default:
|
|
// Doesn't support Connect at all
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func indexKindFromServiceNode(n *structs.ServiceNode) ([]byte, error) {
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(string(n.ServiceKind)))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// indexWithPeerName adds peer name to the index.
|
|
func indexWithPeerName[T peerIndexable](
|
|
fn func(T) ([]byte, error),
|
|
) func(T) ([]byte, error) {
|
|
return func(e T) ([]byte, error) {
|
|
v, err := fn(e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
peername := e.PeerOrEmpty()
|
|
if peername == "" {
|
|
peername = structs.LocalPeerKeyword
|
|
}
|
|
b := newIndexBuilder(len(v) + len(peername) + 1)
|
|
b.String(strings.ToLower(peername))
|
|
b.Raw(v)
|
|
return b.Bytes(), nil
|
|
}
|
|
}
|
|
|
|
// multiIndexWithPeerName adds peer name to multiple indices, and returns multiple indices.
|
|
func multiIndexWithPeerName[T any](
|
|
fn func(T) ([][]byte, error),
|
|
) func(T) ([][]byte, error) {
|
|
return func(raw T) ([][]byte, error) {
|
|
n, ok := any(raw).(peerIndexable)
|
|
if !ok {
|
|
return nil, fmt.Errorf("type must be peerIndexable: %T", raw)
|
|
}
|
|
|
|
results, err := fn(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
peername := n.PeerOrEmpty()
|
|
if peername == "" {
|
|
peername = structs.LocalPeerKeyword
|
|
}
|
|
for i, v := range results {
|
|
b := newIndexBuilder(len(v) + len(peername) + 1)
|
|
b.String(strings.ToLower(peername))
|
|
b.Raw(v)
|
|
results[i] = b.Bytes()
|
|
}
|
|
return results, nil
|
|
}
|
|
}
|
|
|
|
// checksTableSchema returns a new table schema used for storing and indexing
|
|
// health check information. Health checks have a number of different attributes
|
|
// we want to filter by, so this table is a bit more complex.
|
|
func checksTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableChecks,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: indexerSingleWithPrefix[NodeCheckQuery, *structs.HealthCheck, any]{
|
|
readIndex: indexWithPeerName(indexFromNodeCheckQuery),
|
|
writeIndex: indexWithPeerName(indexFromHealthCheck),
|
|
prefixIndex: prefixIndexFromQueryWithPeer,
|
|
},
|
|
},
|
|
indexStatus: {
|
|
Name: indexStatus,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, *structs.HealthCheck]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexStatusFromHealthCheck),
|
|
},
|
|
},
|
|
indexService: {
|
|
Name: indexService,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, *structs.HealthCheck]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexServiceNameFromHealthCheck),
|
|
},
|
|
},
|
|
indexNode: {
|
|
Name: indexNode,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexerSingle[Query, nodeIdentifier]{
|
|
readIndex: indexWithPeerName(indexFromQuery),
|
|
writeIndex: indexWithPeerName(indexFromNodeIdentity),
|
|
},
|
|
},
|
|
indexNodeService: {
|
|
Name: indexNodeService,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: indexerSingle[NodeServiceQuery, *structs.HealthCheck]{
|
|
readIndex: indexWithPeerName(indexFromNodeServiceQuery),
|
|
writeIndex: indexWithPeerName(indexNodeServiceFromHealthCheck),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func indexFromNodeCheckQuery(q NodeCheckQuery) ([]byte, error) {
|
|
if q.Node == "" || q.CheckID == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(q.Node))
|
|
b.String(strings.ToLower(q.CheckID))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexFromHealthCheck(hc *structs.HealthCheck) ([]byte, error) {
|
|
if hc.Node == "" || hc.CheckID == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(hc.Node))
|
|
b.String(strings.ToLower(string(hc.CheckID)))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexNodeServiceFromHealthCheck(hc *structs.HealthCheck) ([]byte, error) {
|
|
if hc.Node == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(hc.Node))
|
|
b.String(strings.ToLower(hc.ServiceID))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexStatusFromHealthCheck(hc *structs.HealthCheck) ([]byte, error) {
|
|
if hc.Status == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(hc.Status))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func indexServiceNameFromHealthCheck(hc *structs.HealthCheck) ([]byte, error) {
|
|
if hc.ServiceName == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(hc.ServiceName))
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// gatewayServicesTableSchema returns a new table schema used to store information
|
|
// about services associated with terminating gateways.
|
|
func gatewayServicesTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableGatewayServices,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&ServiceNameIndex{
|
|
Field: "Gateway",
|
|
},
|
|
&ServiceNameIndex{
|
|
Field: "Service",
|
|
},
|
|
&memdb.IntFieldIndex{
|
|
Field: "Port",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
indexGateway: {
|
|
Name: indexGateway,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &ServiceNameIndex{
|
|
Field: "Gateway",
|
|
},
|
|
},
|
|
indexService: {
|
|
Name: indexService,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &ServiceNameIndex{
|
|
Field: "Service",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// meshTopologyTableSchema returns a new table schema used to store information
|
|
// relating upstream and downstream services
|
|
func meshTopologyTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableMeshTopology,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&ServiceNameIndex{
|
|
Field: "Upstream",
|
|
},
|
|
&ServiceNameIndex{
|
|
Field: "Downstream",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
indexUpstream: {
|
|
Name: indexUpstream,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &ServiceNameIndex{
|
|
Field: "Upstream",
|
|
},
|
|
},
|
|
indexDownstream: {
|
|
Name: indexDownstream,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &ServiceNameIndex{
|
|
Field: "Downstream",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type ServiceNameIndex struct {
|
|
Field string
|
|
}
|
|
|
|
func (index *ServiceNameIndex) FromObject(obj interface{}) (bool, []byte, error) {
|
|
v := reflect.ValueOf(obj)
|
|
v = reflect.Indirect(v) // Dereference the pointer if any
|
|
|
|
fv := v.FieldByName(index.Field)
|
|
isPtr := fv.Kind() == reflect.Ptr
|
|
fv = reflect.Indirect(fv)
|
|
if !isPtr && !fv.IsValid() || !fv.CanInterface() {
|
|
return false, nil,
|
|
fmt.Errorf("field '%s' for %#v is invalid %v ", index.Field, obj, isPtr)
|
|
}
|
|
|
|
name, ok := fv.Interface().(structs.ServiceName)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("Field 'ServiceName' is not of type structs.ServiceName")
|
|
}
|
|
|
|
// Enforce lowercase and add null character as terminator
|
|
id := strings.ToLower(name.String()) + "\x00"
|
|
|
|
return true, []byte(id), nil
|
|
}
|
|
|
|
func (index *ServiceNameIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("must provide only a single argument")
|
|
}
|
|
name, ok := args[0].(structs.ServiceName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be of type structs.ServiceName: %#v", args[0])
|
|
}
|
|
|
|
// Enforce lowercase and add null character as terminator
|
|
id := strings.ToLower(name.String()) + "\x00"
|
|
|
|
return []byte(strings.ToLower(id)), nil
|
|
}
|
|
|
|
func (index *ServiceNameIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
|
val, err := index.FromArgs(args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Strip the null terminator, the rest is a prefix
|
|
n := len(val)
|
|
if n > 0 {
|
|
return val[:n-1], nil
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// upstreamDownstream pairs come from individual proxy registrations, which can be updated independently.
|
|
type upstreamDownstream struct {
|
|
Upstream structs.ServiceName
|
|
Downstream structs.ServiceName
|
|
|
|
// Refs stores the registrations that contain this pairing.
|
|
// When there are no remaining Refs, the upstreamDownstream can be deleted.
|
|
//
|
|
// Note: This map must be treated as immutable when accessed in MemDB.
|
|
// The entire upstreamDownstream structure must be deep copied on updates.
|
|
Refs map[string]struct{}
|
|
|
|
structs.RaftIndex
|
|
}
|
|
|
|
// NodeCheckQuery is used to query the ID index of the checks table.
|
|
type NodeCheckQuery struct {
|
|
Node string
|
|
CheckID string
|
|
PeerName string
|
|
acl.EnterpriseMeta
|
|
}
|
|
|
|
type peerIndexable interface {
|
|
PeerOrEmpty() string
|
|
}
|
|
|
|
func (q NodeCheckQuery) PeerOrEmpty() string {
|
|
return q.PeerName
|
|
}
|
|
|
|
// NamespaceOrDefault exists because structs.EnterpriseMeta uses a pointer
|
|
// receiver for this method. Remove once that is fixed.
|
|
func (q NodeCheckQuery) NamespaceOrDefault() string {
|
|
return q.EnterpriseMeta.NamespaceOrDefault()
|
|
}
|
|
|
|
// PartitionOrDefault exists because structs.EnterpriseMeta uses a pointer
|
|
// receiver for this method. Remove once that is fixed.
|
|
func (q NodeCheckQuery) PartitionOrDefault() string {
|
|
return q.EnterpriseMeta.PartitionOrDefault()
|
|
}
|
|
|
|
// ServiceVirtualIP is used to store a virtual IP associated with a service.
|
|
// It is also used to store assigned virtual IPs when a snapshot is created.
|
|
type ServiceVirtualIP struct {
|
|
Service structs.PeeredServiceName
|
|
IP net.IP
|
|
ManualIPs []string
|
|
|
|
structs.RaftIndex
|
|
}
|
|
|
|
func (s ServiceVirtualIP) IPWithOffset() (string, error) {
|
|
result, err := addIPOffset(startingVirtualIP, s.IP)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return result.String(), nil
|
|
}
|
|
|
|
// FreeVirtualIP is used to store a virtual IP freed up by a service deregistration.
|
|
// It is also used to store free virtual IPs when a snapshot is created.
|
|
type FreeVirtualIP struct {
|
|
IP net.IP
|
|
IsCounter bool
|
|
}
|
|
|
|
func counterIndex(obj interface{}) (bool, error) {
|
|
if vip, ok := obj.(FreeVirtualIP); ok {
|
|
return vip.IsCounter, nil
|
|
}
|
|
return false, fmt.Errorf("object is not a virtual IP entry")
|
|
}
|
|
|
|
type ServiceManualVIPIndex struct{}
|
|
|
|
func (index *ServiceManualVIPIndex) FromObject(obj interface{}) (bool, []byte, error) {
|
|
entry, ok := obj.(ServiceVirtualIP)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("object is not a ServiceVirtualIP")
|
|
}
|
|
|
|
// Enforce lowercase and add null character as terminator
|
|
id := strings.ToLower(entry.Service.ServiceName.PartitionOrDefault()) + "\x00"
|
|
|
|
return true, []byte(id), nil
|
|
}
|
|
|
|
func (index *ServiceManualVIPIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("must provide only a single argument")
|
|
}
|
|
arg, ok := args[0].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
}
|
|
|
|
id := strings.ToLower(arg) + "\x00"
|
|
return []byte(id), nil
|
|
}
|
|
|
|
func serviceVirtualIPTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableServiceVirtualIPs,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: indexerSingleWithPrefix[structs.PeeredServiceName, ServiceVirtualIP, Query]{
|
|
readIndex: indexFromPeeredServiceName,
|
|
writeIndex: indexFromServiceVirtualIP,
|
|
// Read all peers in a cluster / partition
|
|
prefixIndex: prefixIndexFromQueryWithPeerWildcardable,
|
|
},
|
|
},
|
|
indexManualVIPs: {
|
|
Name: indexManualVIPs,
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundMultiIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&ServiceManualVIPIndex{},
|
|
&memdb.StringSliceFieldIndex{
|
|
Field: "ManualIPs",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func indexFromServiceVirtualIP(vip ServiceVirtualIP) ([]byte, error) {
|
|
if vip.Service.ServiceName.Name == "" {
|
|
return nil, errMissingValueForIndex
|
|
}
|
|
return indexFromPeeredServiceName(vip.Service)
|
|
}
|
|
|
|
func freeVirtualIPTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableFreeVirtualIPs,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "IP",
|
|
},
|
|
&memdb.ConditionalIndex{
|
|
Conditional: counterIndex,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
indexCounterOnly: {
|
|
Name: indexCounterOnly,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.ConditionalIndex{
|
|
Conditional: counterIndex,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type KindServiceName struct {
|
|
Kind structs.ServiceKind
|
|
Service structs.ServiceName
|
|
|
|
structs.RaftIndex
|
|
}
|
|
|
|
func (n *KindServiceName) PartitionOrDefault() string {
|
|
return n.Service.PartitionOrDefault()
|
|
}
|
|
|
|
func (n *KindServiceName) NamespaceOrDefault() string {
|
|
return n.Service.NamespaceOrDefault()
|
|
}
|
|
|
|
func kindServiceNameTableSchema() *memdb.TableSchema {
|
|
// TODO(peering): make this peer-aware
|
|
return &memdb.TableSchema{
|
|
Name: tableKindServiceNames,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: indexerSingle[any, any]{
|
|
readIndex: indexFromKindServiceName,
|
|
writeIndex: indexFromKindServiceName,
|
|
},
|
|
},
|
|
indexKind: {
|
|
Name: indexKind,
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: indexerSingle[enterpriseIndexable, enterpriseIndexable]{
|
|
readIndex: indexFromKindServiceNameKindOnly,
|
|
writeIndex: indexFromKindServiceNameKindOnly,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// KindServiceNameQuery is used to lookup service names by kind or enterprise meta.
|
|
type KindServiceNameQuery struct {
|
|
Kind structs.ServiceKind
|
|
Name string
|
|
acl.EnterpriseMeta
|
|
}
|
|
|
|
// NamespaceOrDefault exists because structs.EnterpriseMeta uses a pointer
|
|
// receiver for this method. Remove once that is fixed.
|
|
func (q KindServiceNameQuery) NamespaceOrDefault() string {
|
|
return q.EnterpriseMeta.NamespaceOrDefault()
|
|
}
|
|
|
|
// PartitionOrDefault exists because structs.EnterpriseMeta uses a pointer
|
|
// receiver for this method. Remove once that is fixed.
|
|
func (q KindServiceNameQuery) PartitionOrDefault() string {
|
|
return q.EnterpriseMeta.PartitionOrDefault()
|
|
}
|
|
|
|
func indexFromKindServiceNameKindOnly(raw enterpriseIndexable) ([]byte, error) {
|
|
switch x := raw.(type) {
|
|
case *KindServiceName:
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(string(x.Kind)))
|
|
return b.Bytes(), nil
|
|
|
|
case Query:
|
|
var b indexBuilder
|
|
b.String(strings.ToLower(x.Value))
|
|
return b.Bytes(), nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("type must be *KindServiceName or Query: %T", raw)
|
|
}
|
|
}
|
|
|
|
func kindServiceNamesMaxIndex(tx ReadTxn, ws memdb.WatchSet, kind string) uint64 {
|
|
return maxIndexWatchTxn(tx, ws, kindServiceNameIndexName(kind))
|
|
}
|
|
|
|
func kindServiceNameIndexName(kind string) string {
|
|
return "kind_service_names." + kind
|
|
}
|