Skip to content

Commit

Permalink
Add some extra handling for destination deletes
Browse files Browse the repository at this point in the history
  • Loading branch information
kyhavlov committed Aug 8, 2022
1 parent 6580566 commit fe1fcea
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 70 deletions.
37 changes: 27 additions & 10 deletions agent/consul/state/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/mitchellh/copystructure"

"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/configentry"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
Expand Down Expand Up @@ -2000,7 +2001,8 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st
}

if svc.PeerName == "" {
if err := cleanupGatewayWildcards(tx, idx, svc); err != nil {
sn := structs.ServiceName{Name: svc.ServiceName, EnterpriseMeta: svc.EnterpriseMeta}
if err := cleanupGatewayWildcards(tx, idx, sn, false); err != nil {
return fmt.Errorf("failed to clean up gateway-service associations for %q: %v", name.String(), err)
}
}
Expand Down Expand Up @@ -3656,15 +3658,15 @@ func updateGatewayNamespace(tx WriteTxn, idx uint64, service *structs.GatewaySer
continue
}

supportsIngress, supportsTerminating, err := serviceHasConnectInstances(tx, sn.ServiceName, entMeta)
hasConnectInstance, hasNonConnectInstance, err := serviceHasConnectInstances(tx, sn.ServiceName, entMeta)
if err != nil {
return err
}

if service.GatewayKind == structs.ServiceKindIngressGateway && !supportsIngress {
if service.GatewayKind == structs.ServiceKindIngressGateway && !hasConnectInstance {
continue
}
if service.GatewayKind == structs.ServiceKindTerminatingGateway && !supportsTerminating {
if service.GatewayKind == structs.ServiceKindTerminatingGateway && !hasNonConnectInstance {
continue
}

Expand Down Expand Up @@ -3872,12 +3874,11 @@ func checkGatewayAndUpdate(tx WriteTxn, idx uint64, svc *structs.ServiceName, ki
return nil
}

func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode) error {
func cleanupGatewayWildcards(tx WriteTxn, idx uint64, sn structs.ServiceName, cleaningUpDestination bool) error {
// Clean up association between service name and gateways if needed
sn := structs.ServiceName{Name: svc.ServiceName, EnterpriseMeta: svc.EnterpriseMeta}
gateways, err := tx.Get(tableGatewayServices, indexService, sn)
if err != nil {
return fmt.Errorf("failed gateway lookup for %q: %s", svc.ServiceName, err)
return fmt.Errorf("failed gateway lookup for %q: %s", sn.Name, err)
}

mappings := make([]*structs.GatewayService, 0)
Expand All @@ -3891,11 +3892,27 @@ func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode)
// If there are no connect instances left, ingress gateways with a wildcard entry can remove
// their association with it (same with terminating gateways if there are no non-connect
// instances left).
hasConnectInstance, hasNonConnectInstance, err := serviceHasConnectInstances(tx, svc.ServiceName, &svc.EnterpriseMeta)
hasConnectInstance, hasNonConnectInstance, err := serviceHasConnectInstances(tx, sn.Name, &sn.EnterpriseMeta)
if err != nil {
return err
}

// If we're deleting a service instance but this service is defined as a destination via config entry,
// keep the mapping around.
hasDestination := false
if !cleaningUpDestination {
q := configentry.NewKindName(structs.ServiceDefaults, sn.Name, &sn.EnterpriseMeta)
existing, err := tx.First(tableConfigEntries, indexID, q)
if err != nil {
return fmt.Errorf("failed config entry lookup: %s", err)
}
if existing != nil {
if entry, ok := existing.(*structs.ServiceConfigEntry); ok && entry.Destination != nil {
hasDestination = true
}
}
}

// Do the updates in a separate loop so we don't trash the iterator.
for _, m := range mappings {
// Only delete if association was created by a wildcard specifier.
Expand All @@ -3905,7 +3922,7 @@ func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode)
if m.GatewayKind == structs.ServiceKindIngressGateway && hasConnectInstance {
continue
}
if m.GatewayKind == structs.ServiceKindTerminatingGateway && hasNonConnectInstance {
if m.GatewayKind == structs.ServiceKindTerminatingGateway && (hasNonConnectInstance || hasDestination) {
continue
}

Expand All @@ -3921,7 +3938,7 @@ func cleanupGatewayWildcards(tx WriteTxn, idx uint64, svc *structs.ServiceNode)
} else {
kind, err := GatewayServiceKind(tx, m.Service.Name, &m.Service.EnterpriseMeta)
if err != nil {
return fmt.Errorf("failed to get gateway service kind for service %s: %v", svc.ServiceName, err)
return fmt.Errorf("failed to get gateway service kind for service %s: %v", sn.Name, err)
}
checkGatewayAndUpdate(tx, idx, &structs.ServiceName{Name: m.Service.Name, EnterpriseMeta: m.Service.EnterpriseMeta}, kind)
}
Expand Down
61 changes: 59 additions & 2 deletions agent/consul/state/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5338,13 +5338,70 @@ func TestStateStore_GatewayServices_Terminating(t *testing.T) {
}
assert.Equal(t, expect, out)

// Add a destination via config entry and make sure it's picked up by the wildcard.
configEntryDest := &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "destination1",
Destination: &structs.DestinationConfig{Port: 9000, Addresses: []string{"kafka.test.com"}},
}
assert.NoError(t, s.EnsureConfigEntry(27, configEntryDest))

idx, out, err = s.GatewayServices(ws, "gateway2", nil)
assert.Nil(t, err)
assert.Equal(t, idx, uint64(27))
assert.Len(t, out, 3)

expectWildcardIncludesDest := structs.GatewayServices{
{
Service: structs.NewServiceName("api", nil),
Gateway: structs.NewServiceName("gateway2", nil),
GatewayKind: structs.ServiceKindTerminatingGateway,
FromWildcard: true,
RaftIndex: structs.RaftIndex{
CreateIndex: 26,
ModifyIndex: 26,
},
},
{
Service: structs.NewServiceName("db", nil),
Gateway: structs.NewServiceName("gateway2", nil),
GatewayKind: structs.ServiceKindTerminatingGateway,
FromWildcard: true,
RaftIndex: structs.RaftIndex{
CreateIndex: 26,
ModifyIndex: 26,
},
},
{
Service: structs.NewServiceName("destination1", nil),
Gateway: structs.NewServiceName("gateway2", nil),
GatewayKind: structs.ServiceKindTerminatingGateway,
ServiceKind: structs.GatewayServiceKindDestination,
FromWildcard: true,
RaftIndex: structs.RaftIndex{
CreateIndex: 27,
ModifyIndex: 27,
},
},
}
assert.ElementsMatch(t, expectWildcardIncludesDest, out)

// Delete the destination.
assert.NoError(t, s.DeleteConfigEntry(28, structs.ServiceDefaults, "destination1", nil))

idx, out, err = s.GatewayServices(ws, "gateway2", nil)
assert.Nil(t, err)
assert.Equal(t, idx, uint64(28))
assert.Len(t, out, 2)
assert.Equal(t, expect, out)

// Deleting the config entry should remove existing mappings
assert.Nil(t, s.DeleteConfigEntry(27, "terminating-gateway", "gateway", nil))
assert.Nil(t, s.DeleteConfigEntry(29, "terminating-gateway", "gateway", nil))
assert.True(t, watchFired(ws))

idx, out, err = s.GatewayServices(ws, "gateway", nil)
assert.Nil(t, err)
assert.Equal(t, idx, uint64(27))
assert.Equal(t, idx, uint64(29))
assert.Len(t, out, 0)
}

Expand Down
3 changes: 3 additions & 0 deletions agent/consul/state/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ func deleteConfigEntryTxn(tx WriteTxn, idx uint64, kind, name string, entMeta *a
if err := checkGatewayWildcardsAndUpdate(tx, idx, &serviceName, nil, gsKind); err != nil {
return fmt.Errorf("failed updating gateway mapping: %s", err)
}
if err := cleanupGatewayWildcards(tx, idx, serviceName, true); err != nil {
return fmt.Errorf("failed to cleanup gateway mapping: \"%s\"; err: %v", serviceName, err)
}
if err := checkGatewayAndUpdate(tx, idx, &serviceName, gsKind); err != nil {
return fmt.Errorf("failed updating gateway mapping: %s", err)
}
Expand Down
Loading

0 comments on commit fe1fcea

Please sign in to comment.