Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/domain/config_pool_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ func (f *DefaultConfigPoolGeneratorFactory) CreateGenerator(pool *ConfigPool) (C
if pool.GeneratorConfig == nil {
return nil, NewInvalidInputErrorf("range config pool missing generatorConfig")
}
return NewConfigPoolRangeGenerator(f.valueRepo, pool.ID, *pool.GeneratorConfig), nil
return NewConfigPoolRangeGenerator(f.valueRepo, pool.ID, pool.ParticipantID, *pool.GeneratorConfig), nil
case PoolGeneratorSubnet:
if pool.GeneratorConfig == nil {
return nil, NewInvalidInputErrorf("subnet config pool missing generatorConfig")
}
return NewConfigPoolSubnetGenerator(f.valueRepo, pool.ID, *pool.GeneratorConfig), nil
return NewConfigPoolSubnetGenerator(f.valueRepo, pool.ID, pool.ParticipantID, *pool.GeneratorConfig), nil
default:
return nil, NewInvalidInputErrorf("unsupported config pool generator type: %s", pool.GeneratorType)
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/domain/config_pool_generator_range.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (
// skipping excluded values. It reuses a freed value once its retention cooldown has
// elapsed, otherwise mints a new ConfigPoolValue row.
type ConfigPoolRangeGenerator struct {
repo ConfigPoolValueRepository
poolID properties.UUID
config properties.JSON
repo ConfigPoolValueRepository
poolID properties.UUID
participantID *properties.UUID
config properties.JSON
}

func NewConfigPoolRangeGenerator(repo ConfigPoolValueRepository, poolID properties.UUID, config properties.JSON) *ConfigPoolRangeGenerator {
return &ConfigPoolRangeGenerator{repo: repo, poolID: poolID, config: config}
func NewConfigPoolRangeGenerator(repo ConfigPoolValueRepository, poolID properties.UUID, participantID *properties.UUID, config properties.JSON) *ConfigPoolRangeGenerator {
return &ConfigPoolRangeGenerator{repo: repo, poolID: poolID, participantID: participantID, config: config}
}

func (g *ConfigPoolRangeGenerator) Allocate(ctx context.Context, entityType ConfigPoolValueEntityType, entityID properties.UUID, propertyName string) (any, error) {
Expand Down Expand Up @@ -71,7 +72,7 @@ func (g *ConfigPoolRangeGenerator) Allocate(ctx context.Context, entityType Conf
}
return row.RawValue(), nil
}
value := &ConfigPoolValue{Name: strconv.Itoa(n), Value: n, ConfigPoolID: g.poolID}
value := &ConfigPoolValue{Name: strconv.Itoa(n), Value: n, ConfigPoolID: g.poolID, ParticipantID: g.participantID}
value.Allocate(entityType, entityID, propertyName)
if err := g.repo.Create(ctx, value); err != nil {
return nil, fmt.Errorf("failed to allocate value: %w", err)
Expand Down
47 changes: 41 additions & 6 deletions pkg/domain/config_pool_generator_range_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestConfigPoolRangeGenerator_Allocate(t *testing.T) {
})).Return(nil)
}

gen := NewConfigPoolRangeGenerator(repo, poolID, tt.config)
gen := NewConfigPoolRangeGenerator(repo, poolID, nil, tt.config)
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeInfrastructure, entityID, "asn")

if tt.wantErr {
Expand All @@ -95,6 +95,41 @@ func TestConfigPoolRangeGenerator_Allocate(t *testing.T) {
}
}

// A minted value inherits the pool's participant so participant-scoped allocations stay
// visible only to that participant; a global pool leaves it nil.
func TestConfigPoolRangeGenerator_StampsParticipant(t *testing.T) {
ctx := context.Background()
poolID := properties.UUID(uuid.New())
entityID := properties.UUID(uuid.New())
participantID := properties.UUID(uuid.New())

tests := []struct {
name string
participant *properties.UUID
}{
{name: "stamps nil participant from global pool", participant: nil},
{name: "stamps participant from scoped pool", participant: &participantID},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := NewMockConfigPoolValueRepository(t)
repo.On("FindByPool", ctx, poolID).Return([]*ConfigPoolValue{}, nil)
repo.On("Create", ctx, mock.MatchedBy(func(v *ConfigPoolValue) bool {
if tt.participant == nil {
return v.ParticipantID == nil
}
return v.ParticipantID != nil && *v.ParticipantID == *tt.participant
})).Return(nil)

gen := NewConfigPoolRangeGenerator(repo, poolID, tt.participant, properties.JSON{"min": float64(1), "max": float64(3)})
if _, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeAgent, entityID, "asn"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}

// Release keeps the row: it clears the allocation fields and Updates each value of
// this pool, so the value stays "used" and is never re-allocated. Values of other
// pools are left untouched.
Expand All @@ -120,7 +155,7 @@ func TestConfigPoolRangeGenerator_Release(t *testing.T) {
v.AgentID == nil && v.InfrastructureID == nil && v.AllocatedAt == nil && v.PropertyName == nil
})).Return(nil).Twice()

gen := NewConfigPoolRangeGenerator(repo, poolID, properties.JSON{})
gen := NewConfigPoolRangeGenerator(repo, poolID, nil, properties.JSON{})
if err := gen.Release(ctx, values); err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down Expand Up @@ -148,7 +183,7 @@ func TestConfigPoolRangeGenerator_ReusesReleasedValue(t *testing.T) {
v.AgentID != nil && *v.AgentID == agentID && v.ReleasedAt == nil
})).Return(nil)

gen := NewConfigPoolRangeGenerator(repo, poolID, properties.JSON{"min": float64(1), "max": float64(3)})
gen := NewConfigPoolRangeGenerator(repo, poolID, nil, properties.JSON{"min": float64(1), "max": float64(3)})
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeAgent, agentID, "asn")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -181,7 +216,7 @@ func TestConfigPoolRangeGenerator_RetentionCooldown(t *testing.T) {
return ok && n == 2
})).Return(nil)

gen := NewConfigPoolRangeGenerator(repo, poolID, config)
gen := NewConfigPoolRangeGenerator(repo, poolID, nil, config)
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeAgent, agentID, "asn")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand All @@ -205,7 +240,7 @@ func TestConfigPoolRangeGenerator_RetentionCooldown(t *testing.T) {
return v.ID == released.ID && v.IsAllocated() && v.ReleasedAt == nil
})).Return(nil)

gen := NewConfigPoolRangeGenerator(repo, poolID, config)
gen := NewConfigPoolRangeGenerator(repo, poolID, nil, config)
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeAgent, agentID, "asn")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -237,7 +272,7 @@ func TestConfigPoolRangeGenerator_NeverReallocate(t *testing.T) {
return ok && n == 2
})).Return(nil)

gen := NewConfigPoolRangeGenerator(repo, poolID, properties.JSON{"min": float64(1), "max": float64(3), "retentionSeconds": float64(3600), "neverReallocate": true})
gen := NewConfigPoolRangeGenerator(repo, poolID, nil, properties.JSON{"min": float64(1), "max": float64(3), "retentionSeconds": float64(3600), "neverReallocate": true})
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeAgent, agentID, "asn")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down
13 changes: 7 additions & 6 deletions pkg/domain/config_pool_generator_subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import (
// "hosts" config (defaulting to {host1, host2}), pre-computing them so config templates
// need no IP math.
type ConfigPoolSubnetGenerator struct {
repo ConfigPoolValueRepository
poolID properties.UUID
config properties.JSON
repo ConfigPoolValueRepository
poolID properties.UUID
participantID *properties.UUID
config properties.JSON
}

func NewConfigPoolSubnetGenerator(repo ConfigPoolValueRepository, poolID properties.UUID, config properties.JSON) *ConfigPoolSubnetGenerator {
return &ConfigPoolSubnetGenerator{repo: repo, poolID: poolID, config: config}
func NewConfigPoolSubnetGenerator(repo ConfigPoolValueRepository, poolID properties.UUID, participantID *properties.UUID, config properties.JSON) *ConfigPoolSubnetGenerator {
return &ConfigPoolSubnetGenerator{repo: repo, poolID: poolID, participantID: participantID, config: config}
}

func (g *ConfigPoolSubnetGenerator) Allocate(ctx context.Context, entityType ConfigPoolValueEntityType, entityID properties.UUID, propertyName string) (any, error) {
Expand Down Expand Up @@ -75,7 +76,7 @@ func (g *ConfigPoolSubnetGenerator) Allocate(ctx context.Context, entityType Con
return row.RawValue(), nil
}

cv := &ConfigPoolValue{Name: name, Value: value, ConfigPoolID: g.poolID}
cv := &ConfigPoolValue{Name: name, Value: value, ConfigPoolID: g.poolID, ParticipantID: g.participantID}
cv.Allocate(entityType, entityID, propertyName)
if err := g.repo.Create(ctx, cv); err != nil {
return nil, fmt.Errorf("failed to allocate value: %w", err)
Expand Down
45 changes: 40 additions & 5 deletions pkg/domain/config_pool_generator_subnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func TestConfigPoolSubnetGenerator_Allocate(t *testing.T) {
})).Return(nil)
}

gen := NewConfigPoolSubnetGenerator(repo, poolID, tt.config)
gen := NewConfigPoolSubnetGenerator(repo, poolID, nil, tt.config)
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeInfrastructure, entityID, "ptpSubnet")

if tt.wantErr {
Expand All @@ -188,6 +188,41 @@ func TestConfigPoolSubnetGenerator_Allocate(t *testing.T) {
}
}

// A minted value inherits the pool's participant so participant-scoped allocations stay
// visible only to that participant; a global pool leaves it nil.
func TestConfigPoolSubnetGenerator_StampsParticipant(t *testing.T) {
ctx := context.Background()
poolID := properties.UUID(uuid.New())
entityID := properties.UUID(uuid.New())
participantID := properties.UUID(uuid.New())

tests := []struct {
name string
participant *properties.UUID
}{
{name: "stamps nil participant from global pool", participant: nil},
{name: "stamps participant from scoped pool", participant: &participantID},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := NewMockConfigPoolValueRepository(t)
repo.On("FindByPool", ctx, poolID).Return([]*ConfigPoolValue{}, nil)
repo.On("Create", ctx, mock.MatchedBy(func(v *ConfigPoolValue) bool {
if tt.participant == nil {
return v.ParticipantID == nil
}
return v.ParticipantID != nil && *v.ParticipantID == *tt.participant
})).Return(nil)

gen := NewConfigPoolSubnetGenerator(repo, poolID, tt.participant, properties.JSON{"cidr": "10.0.0.0/30"})
if _, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeInfrastructure, entityID, "subnet"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}

// Release keeps the row: it clears the allocation fields and Updates each value of this
// pool, leaving other pools' values untouched, so a released subnet is never re-issued.
func TestConfigPoolSubnetGenerator_Release(t *testing.T) {
Expand All @@ -212,7 +247,7 @@ func TestConfigPoolSubnetGenerator_Release(t *testing.T) {
v.AgentID == nil && v.InfrastructureID == nil && v.AllocatedAt == nil && v.PropertyName == nil
})).Return(nil).Twice()

gen := NewConfigPoolSubnetGenerator(repo, poolID, properties.JSON{})
gen := NewConfigPoolSubnetGenerator(repo, poolID, nil, properties.JSON{})
if err := gen.Release(ctx, values); err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand All @@ -238,7 +273,7 @@ func TestConfigPoolSubnetGenerator_ReusesReleasedValue(t *testing.T) {
return v.ID == released.ID && v.IsAllocated() && v.ReleasedAt == nil
})).Return(nil)

gen := NewConfigPoolSubnetGenerator(repo, poolID, properties.JSON{"cidr": "212.78.11.0/24", "excludeFirst": float64(2)})
gen := NewConfigPoolSubnetGenerator(repo, poolID, nil, properties.JSON{"cidr": "212.78.11.0/24", "excludeFirst": float64(2)})
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeInfrastructure, entityID, "ip")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -268,7 +303,7 @@ func TestConfigPoolSubnetGenerator_RetentionHoldsValue(t *testing.T) {
return v.Value == "212.78.11.3"
})).Return(nil)

gen := NewConfigPoolSubnetGenerator(repo, poolID, properties.JSON{"cidr": "212.78.11.0/24", "excludeFirst": float64(2), "retentionSeconds": float64(3600)})
gen := NewConfigPoolSubnetGenerator(repo, poolID, nil, properties.JSON{"cidr": "212.78.11.0/24", "excludeFirst": float64(2), "retentionSeconds": float64(3600)})
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeInfrastructure, entityID, "ip")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down Expand Up @@ -298,7 +333,7 @@ func TestConfigPoolSubnetGenerator_NeverReallocate(t *testing.T) {
return v.Value == "212.78.11.3"
})).Return(nil)

gen := NewConfigPoolSubnetGenerator(repo, poolID, properties.JSON{"cidr": "212.78.11.0/24", "excludeFirst": float64(2), "neverReallocate": true})
gen := NewConfigPoolSubnetGenerator(repo, poolID, nil, properties.JSON{"cidr": "212.78.11.0/24", "excludeFirst": float64(2), "neverReallocate": true})
got, err := gen.Allocate(ctx, ConfigPoolValueEntityTypeInfrastructure, entityID, "ip")
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/service_pool_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (f *DefaultGeneratorFactory) CreateGenerator(pool *ServicePool) (PoolGenera
if pool.GeneratorConfig == nil {
return nil, NewInvalidInputErrorf("subnet pool missing generator config")
}
return NewSubnetGenerator(f.valueRepo, pool.ID, *pool.GeneratorConfig), nil
return NewSubnetGenerator(f.valueRepo, pool.ID, pool.ParticipantID, *pool.GeneratorConfig), nil
default:
return nil, NewInvalidInputErrorf("unsupported pool generator type: %s", pool.GeneratorType)
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/domain/service_pool_generator_subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import (
type SubnetGenerator struct {
valueRepo ServicePoolValueRepository
poolID properties.UUID
participantID *properties.UUID
generatorConfig properties.JSON
}

// NewSubnetGenerator creates a new subnet-based generator
func NewSubnetGenerator(valueRepo ServicePoolValueRepository, poolID properties.UUID, config properties.JSON) *SubnetGenerator {
func NewSubnetGenerator(valueRepo ServicePoolValueRepository, poolID properties.UUID, participantID *properties.UUID, config properties.JSON) *SubnetGenerator {
return &SubnetGenerator{
valueRepo: valueRepo,
poolID: poolID,
participantID: participantID,
generatorConfig: config,
}
}
Expand Down Expand Up @@ -124,6 +126,7 @@ func (g *SubnetGenerator) Allocate(ctx context.Context, serviceID properties.UUI
Name: ipStr,
Value: ipStr, // Store IP address as a plain string
ServicePoolID: g.poolID,
ParticipantID: g.participantID,
}
newValue.Allocate(serviceID, propertyName)
if err := g.valueRepo.Create(ctx, newValue); err != nil {
Expand Down
44 changes: 39 additions & 5 deletions pkg/domain/service_pool_generator_subnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func TestSubnetGenerator_Allocate(t *testing.T) {
repo := NewMockServicePoolValueRepository(t)
tt.setupMock(repo)

generator := NewSubnetGenerator(repo, poolID, tt.generatorConfig)
generator := NewSubnetGenerator(repo, poolID, nil, tt.generatorConfig)
value, err := generator.Allocate(ctx, serviceID, propertyName)

if tt.expectErr {
Expand All @@ -139,6 +139,40 @@ func TestSubnetGenerator_Allocate(t *testing.T) {
}
}

// A minted value inherits the pool's participant so participant-scoped allocations stay
// visible only to that participant; a global pool leaves it nil.
func TestSubnetGenerator_StampsParticipant(t *testing.T) {
ctx := context.Background()
poolID := properties.UUID(uuid.New())
serviceID := properties.UUID(uuid.New())
participantID := properties.UUID(uuid.New())

tests := []struct {
name string
participant *properties.UUID
}{
{name: "stamps nil participant from global pool", participant: nil},
{name: "stamps participant from scoped pool", participant: &participantID},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := NewMockServicePoolValueRepository(t)
repo.EXPECT().FindByPool(ctx, poolID).Return([]*ServicePoolValue{}, nil)
repo.EXPECT().Create(ctx, mock.MatchedBy(func(v *ServicePoolValue) bool {
if tt.participant == nil {
return v.ParticipantID == nil
}
return v.ParticipantID != nil && *v.ParticipantID == *tt.participant
})).Return(nil)

generator := NewSubnetGenerator(repo, poolID, tt.participant, properties.JSON{"cidr": "10.0.0.0/30", "excludeFirst": 1})
_, err := generator.Allocate(ctx, serviceID, "ipAddress")
require.NoError(t, err)
})
}
}

func TestSubnetGenerator_Release(t *testing.T) {
ctx := context.Background()
poolID := properties.UUID(uuid.New())
Expand Down Expand Up @@ -206,7 +240,7 @@ func TestSubnetGenerator_Release(t *testing.T) {
repo := NewMockServicePoolValueRepository(t)
tt.setupMock(repo)

generator := NewSubnetGenerator(repo, poolID, config)
generator := NewSubnetGenerator(repo, poolID, nil, config)
err := generator.Release(ctx, serviceID)

if tt.expectErr {
Expand Down Expand Up @@ -244,7 +278,7 @@ func TestSubnetGenerator_ReusesReleasedValue(t *testing.T) {
})).
Return(nil)

generator := NewSubnetGenerator(repo, poolID, properties.JSON{"cidr": "10.0.0.0/24", "excludeFirst": 1})
generator := NewSubnetGenerator(repo, poolID, nil, properties.JSON{"cidr": "10.0.0.0/24", "excludeFirst": 1})
value, err := generator.Allocate(ctx, serviceID, "ipAddress")
require.NoError(t, err)
assert.Equal(t, "10.0.0.1", value)
Expand Down Expand Up @@ -274,7 +308,7 @@ func TestSubnetGenerator_RetentionHoldsValue(t *testing.T) {
})).
Return(nil)

generator := NewSubnetGenerator(repo, poolID, properties.JSON{"cidr": "10.0.0.0/24", "excludeFirst": 1, "retentionSeconds": float64(3600)})
generator := NewSubnetGenerator(repo, poolID, nil, properties.JSON{"cidr": "10.0.0.0/24", "excludeFirst": 1, "retentionSeconds": float64(3600)})
value, err := generator.Allocate(ctx, serviceID, "ipAddress")
require.NoError(t, err)
assert.Equal(t, "10.0.0.2", value)
Expand Down Expand Up @@ -304,7 +338,7 @@ func TestSubnetGenerator_NeverReallocate(t *testing.T) {
})).
Return(nil)

generator := NewSubnetGenerator(repo, poolID, properties.JSON{"cidr": "10.0.0.0/24", "excludeFirst": 1, "neverReallocate": true})
generator := NewSubnetGenerator(repo, poolID, nil, properties.JSON{"cidr": "10.0.0.0/24", "excludeFirst": 1, "neverReallocate": true})
value, err := generator.Allocate(ctx, serviceID, "ipAddress")
require.NoError(t, err)
assert.Equal(t, "10.0.0.2", value)
Expand Down
Loading
Loading