Attach service virtual IP info to compiled discovery chain (#17295)

* 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
This commit is contained in:
Kyle Havlovitz 2023-05-11 19:28:16 -07:00 committed by GitHub
parent b6d5d5649d
commit 73897656d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 5 deletions

View File

@ -261,6 +261,8 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) {
33*time.Second, 33*time.Second,
), ),
}, },
AutoVirtualIPs: []string{"240.0.0.1"},
ManualVirtualIPs: []string{},
}, },
} }
require.Equal(t, expect, resp) require.Equal(t, expect, resp)

View File

@ -44,6 +44,11 @@ type CompileRequest struct {
OverrideConnectTimeout time.Duration OverrideConnectTimeout time.Duration
Entries *configentry.DiscoveryChainSet Entries *configentry.DiscoveryChainSet
// AutoVirtualIPs and ManualVirtualIPs are lists of IPs associated with
// the service.
AutoVirtualIPs []string
ManualVirtualIPs []string
} }
// Compile assembles a discovery chain in the form of a graph of nodes using // Compile assembles a discovery chain in the form of a graph of nodes using
@ -98,6 +103,8 @@ func Compile(req CompileRequest) (*structs.CompiledDiscoveryChain, error) {
overrideProtocol: req.OverrideProtocol, overrideProtocol: req.OverrideProtocol,
overrideConnectTimeout: req.OverrideConnectTimeout, overrideConnectTimeout: req.OverrideConnectTimeout,
entries: entries, entries: entries,
autoVirtualIPs: req.AutoVirtualIPs,
manualVirtualIPs: req.ManualVirtualIPs,
resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry), resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry),
splitterNodes: make(map[string]*structs.DiscoveryGraphNode), splitterNodes: make(map[string]*structs.DiscoveryGraphNode),
@ -139,6 +146,11 @@ type compiler struct {
// This is an INPUT field. // This is an INPUT field.
entries *configentry.DiscoveryChainSet entries *configentry.DiscoveryChainSet
// autoVirtualIPs and manualVirtualIPs are lists of IPs associated with
// the service.
autoVirtualIPs []string
manualVirtualIPs []string
// resolvers is initially seeded by copying the provided entries.Resolvers // resolvers is initially seeded by copying the provided entries.Resolvers
// map and default resolvers are added as they are needed. // map and default resolvers are added as they are needed.
resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry
@ -352,6 +364,8 @@ func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) {
StartNode: c.startNode, StartNode: c.startNode,
Nodes: c.nodes, Nodes: c.nodes,
Targets: c.loadedTargets, Targets: c.loadedTargets,
AutoVirtualIPs: c.autoVirtualIPs,
ManualVirtualIPs: c.manualVirtualIPs,
}, nil }, nil
} }

View File

@ -3045,11 +3045,7 @@ func (s *Store) VirtualIPForService(psn structs.PeeredServiceName) (string, erro
return "", nil return "", nil
} }
result, err := addIPOffset(startingVirtualIP, vip.(ServiceVirtualIP).IP) return vip.(ServiceVirtualIP).IPWithOffset()
if err != nil {
return "", err
}
return result.String(), nil
} }
func (s *Store) ServiceVirtualIPs() (uint64, []ServiceVirtualIP, error) { func (s *Store) ServiceVirtualIPs() (uint64, []ServiceVirtualIP, error) {
@ -3080,6 +3076,10 @@ func (s *Store) ServiceManualVIPs(psn structs.PeeredServiceName) (*ServiceVirtua
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
return serviceVIPsTxn(tx, psn)
}
func serviceVIPsTxn(tx ReadTxn, psn structs.PeeredServiceName) (*ServiceVirtualIP, error) {
vip, err := tx.First(tableServiceVirtualIPs, indexID, psn) vip, err := tx.First(tableServiceVirtualIPs, indexID, psn)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed service virtual IP lookup: %s", err) return nil, fmt.Errorf("failed service virtual IP lookup: %s", err)

View File

@ -616,6 +616,14 @@ type ServiceVirtualIP struct {
structs.RaftIndex 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. // 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. // It is also used to store free virtual IPs when a snapshot is created.
type FreeVirtualIP struct { type FreeVirtualIP struct {

View File

@ -1371,6 +1371,22 @@ func serviceDiscoveryChainTxn(
} }
req.EvaluateInTrustDomain = signingID.Host() req.EvaluateInTrustDomain = signingID.Host()
psn := structs.PeeredServiceName{ServiceName: structs.NewServiceName(serviceName, entMeta)}
serviceVIPEntry, err := serviceVIPsTxn(tx, psn)
if err != nil {
return 0, nil, nil, err
}
if serviceVIPEntry != nil {
assignedIP, err := serviceVIPEntry.IPWithOffset()
if err != nil {
return 0, nil, nil, err
}
req.AutoVirtualIPs = []string{assignedIP}
req.ManualVirtualIPs = make([]string, len(serviceVIPEntry.ManualIPs))
copy(req.ManualVirtualIPs, serviceVIPEntry.ManualIPs)
}
// Then we compile it into something useful. // Then we compile it into something useful.
chain, err := discoverychain.Compile(req) chain, err := discoverychain.Compile(req)
if err != nil { if err != nil {

View File

@ -9,9 +9,12 @@ import (
"time" "time"
memdb "github.com/hashicorp/go-memdb" memdb "github.com/hashicorp/go-memdb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/configentry"
"github.com/hashicorp/consul/agent/consul/discoverychain"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/proto/private/pbpeering" "github.com/hashicorp/consul/proto/private/pbpeering"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
@ -3389,3 +3392,51 @@ func writeConfigAndBumpIndexForTest(s *Store, idx uint64, entry structs.ConfigEn
} }
return idx, err return idx, err
} }
func TestStateStore_DiscoveryChain_AttachVirtualIPs(t *testing.T) {
s := testStateStore(t)
setVirtualIPFlags(t, s)
ca := &structs.CAConfiguration{
Provider: "consul",
}
err := s.CASetConfig(0, ca)
require.NoError(t, err)
// Attempt to assign manual virtual IPs to a service that doesn't exist - should be a no-op.
psn := structs.PeeredServiceName{ServiceName: structs.ServiceName{Name: "foo", EnterpriseMeta: *acl.DefaultEnterpriseMeta()}}
// Register a service via config entry.
s.EnsureConfigEntry(1, &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "foo",
})
vip, err := s.VirtualIPForService(psn)
require.NoError(t, err)
assert.Equal(t, "240.0.0.1", vip)
// Assign manual virtual IPs for foo
found, _, err := s.AssignManualServiceVIPs(2, psn, []string{"2.2.2.2", "3.3.3.3"})
require.NoError(t, err)
require.True(t, found)
serviceVIP, err := s.ServiceManualVIPs(psn)
require.NoError(t, err)
require.Equal(t, uint64(2), serviceVIP.ModifyIndex)
require.Equal(t, "0.0.0.1", serviceVIP.IP.String())
require.Equal(t, []string{"2.2.2.2", "3.3.3.3"}, serviceVIP.ManualIPs)
req := discoverychain.CompileRequest{
ServiceName: "foo",
EvaluateInNamespace: psn.ServiceName.NamespaceOrDefault(),
EvaluateInPartition: psn.ServiceName.PartitionOrDefault(),
EvaluateInDatacenter: "dc1",
}
idx, chain, _, err := s.ServiceDiscoveryChain(nil, "foo", structs.DefaultEnterpriseMetaInDefaultPartition(), req)
require.NoError(t, err)
require.Equal(t, uint64(1), idx)
require.Equal(t, []string{"240.0.0.1"}, chain.AutoVirtualIPs)
require.Equal(t, []string{"2.2.2.2", "3.3.3.3"}, chain.ManualVirtualIPs)
}

View File

@ -285,6 +285,8 @@ func TestDiscoveryChainRead(t *testing.T) {
33*time.Second, 33*time.Second,
), ),
}, },
AutoVirtualIPs: []string{"240.0.0.1"},
ManualVirtualIPs: []string{},
} }
if !reflect.DeepEqual(expect, value.Chain) { if !reflect.DeepEqual(expect, value.Chain) {
r.Fatalf("should be equal: expected=%+v, got=%+v", expect, value.Chain) r.Fatalf("should be equal: expected=%+v, got=%+v", expect, value.Chain)
@ -333,6 +335,8 @@ func TestDiscoveryChainRead(t *testing.T) {
expectTarget_DC1.ID: expectTarget_DC1, expectTarget_DC1.ID: expectTarget_DC1,
expectTarget_DC2.ID: expectTarget_DC2, expectTarget_DC2.ID: expectTarget_DC2,
}, },
AutoVirtualIPs: []string{"240.0.0.1"},
ManualVirtualIPs: []string{},
} }
require.True(t, t.Run("POST: read modified chain with overrides (camel case)", func(t *testing.T) { require.True(t, t.Run("POST: read modified chain with overrides (camel case)", func(t *testing.T) {

View File

@ -57,6 +57,10 @@ type CompiledDiscoveryChain struct {
// Targets is a list of all targets used in this chain. // Targets is a list of all targets used in this chain.
Targets map[string]*DiscoveryTarget `json:",omitempty"` Targets map[string]*DiscoveryTarget `json:",omitempty"`
// VirtualIPs is a list of virtual IPs associated with the service.
AutoVirtualIPs []string
ManualVirtualIPs []string
} }
// ID returns an ID that encodes the service, namespace, partition, and datacenter. // ID returns an ID that encodes the service, namespace, partition, and datacenter.

View File

@ -133,6 +133,14 @@ func (o *CompiledDiscoveryChain) DeepCopy() *CompiledDiscoveryChain {
cp.Targets[k2] = cp_Targets_v2 cp.Targets[k2] = cp_Targets_v2
} }
} }
if o.AutoVirtualIPs != nil {
cp.AutoVirtualIPs = make([]string, len(o.AutoVirtualIPs))
copy(cp.AutoVirtualIPs, o.AutoVirtualIPs)
}
if o.ManualVirtualIPs != nil {
cp.ManualVirtualIPs = make([]string, len(o.ManualVirtualIPs))
copy(cp.ManualVirtualIPs, o.ManualVirtualIPs)
}
return &cp return &cp
} }