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
304 changes: 304 additions & 0 deletions cloudemu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/stackshy/cloudemu/storage"
storagedriver "github.com/stackshy/cloudemu/storage/driver"

lbdriver "github.com/stackshy/cloudemu/loadbalancer/driver"

cachedriver "github.com/stackshy/cloudemu/cache/driver"
crdriver "github.com/stackshy/cloudemu/containerregistry/driver"
ebdriver "github.com/stackshy/cloudemu/eventbus/driver"
Expand Down Expand Up @@ -6479,3 +6481,305 @@ func TestEncryptionConfigGCP(t *testing.T) {
t.Error("expected encryption enabled")
}
}

func TestListenerRulesAWS(t *testing.T) {
ctx := context.Background()
p := NewAWS()

lb, err := p.ELB.CreateLoadBalancer(ctx, lbdriver.LBConfig{
Name: "test-lb", Type: "application", Scheme: "internet-facing",
})
if err != nil {
t.Fatal(err)
}

tg, err := p.ELB.CreateTargetGroup(ctx, lbdriver.TargetGroupConfig{
Name: "test-tg", Protocol: "HTTP", Port: 80, VPCID: "vpc-1",
})
if err != nil {
t.Fatal(err)
}

li, err := p.ELB.CreateListener(ctx, lbdriver.ListenerConfig{
LBARN: lb.ARN, Protocol: "HTTP", Port: 80, TargetGroupARN: tg.ARN,
})
if err != nil {
t.Fatal(err)
}

// Create rules with path conditions
rule1, err := p.ELB.CreateRule(ctx, lbdriver.RuleConfig{
ListenerARN: li.ARN,
Priority: 10,
Conditions: []lbdriver.RuleCondition{{Field: "path-pattern", Values: []string{"/api/*"}}},
Actions: []lbdriver.RuleAction{{Type: "forward", TargetGroupARN: tg.ARN}},
})
if err != nil {
t.Fatal(err)
}

if rule1.ARN == "" {
t.Error("expected non-empty rule ARN")
}

if rule1.Priority != 10 {
t.Errorf("expected priority 10, got %d", rule1.Priority)
}

_, err = p.ELB.CreateRule(ctx, lbdriver.RuleConfig{
ListenerARN: li.ARN,
Priority: 20,
Conditions: []lbdriver.RuleCondition{{Field: "host-header", Values: []string{"example.com"}}},
Actions: []lbdriver.RuleAction{{Type: "forward", TargetGroupARN: tg.ARN}},
})
if err != nil {
t.Fatal(err)
}

// Describe rules
rules, err := p.ELB.DescribeRules(ctx, li.ARN)
if err != nil {
t.Fatal(err)
}

if len(rules) != 2 {
t.Errorf("expected 2 rules, got %d", len(rules))
}

// Delete a rule
if err := p.ELB.DeleteRule(ctx, rule1.ARN); err != nil {
t.Fatal(err)
}

rules, err = p.ELB.DescribeRules(ctx, li.ARN)
if err != nil {
t.Fatal(err)
}

if len(rules) != 1 {
t.Errorf("expected 1 rule after deletion, got %d", len(rules))
}
}

func TestModifyListenerAWS(t *testing.T) {
ctx := context.Background()
p := NewAWS()

lb, err := p.ELB.CreateLoadBalancer(ctx, lbdriver.LBConfig{
Name: "test-lb", Type: "application", Scheme: "internet-facing",
})
if err != nil {
t.Fatal(err)
}

tg, err := p.ELB.CreateTargetGroup(ctx, lbdriver.TargetGroupConfig{
Name: "test-tg", Protocol: "HTTP", Port: 80, VPCID: "vpc-1",
})
if err != nil {
t.Fatal(err)
}

li, err := p.ELB.CreateListener(ctx, lbdriver.ListenerConfig{
LBARN: lb.ARN, Protocol: "HTTP", Port: 80, TargetGroupARN: tg.ARN,
})
if err != nil {
t.Fatal(err)
}

// Modify port
if err := p.ELB.ModifyListener(ctx, lbdriver.ModifyListenerInput{
ListenerARN: li.ARN, Port: 8080,
}); err != nil {
t.Fatal(err)
}

listeners, err := p.ELB.DescribeListeners(ctx, lb.ARN)
if err != nil {
t.Fatal(err)
}

if len(listeners) != 1 {
t.Fatalf("expected 1 listener, got %d", len(listeners))
}

if listeners[0].Port != 8080 {
t.Errorf("expected port 8080, got %d", listeners[0].Port)
}
}

func TestLBAttributesAWS(t *testing.T) {
ctx := context.Background()
p := NewAWS()

lb, err := p.ELB.CreateLoadBalancer(ctx, lbdriver.LBConfig{
Name: "test-lb", Type: "application", Scheme: "internet-facing",
})
if err != nil {
t.Fatal(err)
}

// Get default attributes
attrs, err := p.ELB.GetLBAttributes(ctx, lb.ARN)
if err != nil {
t.Fatal(err)
}

if attrs.IdleTimeout != 60 {
t.Errorf("expected default idle timeout 60, got %d", attrs.IdleTimeout)
}

// Put custom attributes
if err := p.ELB.PutLBAttributes(ctx, lb.ARN, lbdriver.LBAttributes{
IdleTimeout: 120,
DeletionProtection: true,
AccessLogsEnabled: true,
AccessLogsBucket: "my-access-logs",
}); err != nil {
t.Fatal(err)
}

attrs, err = p.ELB.GetLBAttributes(ctx, lb.ARN)
if err != nil {
t.Fatal(err)
}

if attrs.IdleTimeout != 120 {
t.Errorf("expected idle timeout 120, got %d", attrs.IdleTimeout)
}

if !attrs.DeletionProtection {
t.Error("expected deletion protection enabled")
}

if !attrs.AccessLogsEnabled {
t.Error("expected access logs enabled")
}

if attrs.AccessLogsBucket != "my-access-logs" {
t.Errorf("expected bucket 'my-access-logs', got %q", attrs.AccessLogsBucket)
}
}

func TestListenerRulesAzure(t *testing.T) {
ctx := context.Background()
p := NewAzure()

lb, err := p.LB.CreateLoadBalancer(ctx, lbdriver.LBConfig{
Name: "test-lb", Type: "application", Scheme: "internet-facing",
})
if err != nil {
t.Fatal(err)
}

tg, err := p.LB.CreateTargetGroup(ctx, lbdriver.TargetGroupConfig{
Name: "test-tg", Protocol: "HTTP", Port: 80, VPCID: "vnet-1",
})
if err != nil {
t.Fatal(err)
}

li, err := p.LB.CreateListener(ctx, lbdriver.ListenerConfig{
LBARN: lb.ARN, Protocol: "HTTP", Port: 80, TargetGroupARN: tg.ARN,
})
if err != nil {
t.Fatal(err)
}

rule, err := p.LB.CreateRule(ctx, lbdriver.RuleConfig{
ListenerARN: li.ARN,
Priority: 10,
Conditions: []lbdriver.RuleCondition{{Field: "path-pattern", Values: []string{"/api/*"}}},
Actions: []lbdriver.RuleAction{{Type: "forward", TargetGroupARN: tg.ARN}},
})
if err != nil {
t.Fatal(err)
}

if rule.ARN == "" {
t.Error("expected non-empty rule ARN")
}

rules, err := p.LB.DescribeRules(ctx, li.ARN)
if err != nil {
t.Fatal(err)
}

if len(rules) != 1 {
t.Errorf("expected 1 rule, got %d", len(rules))
}

if err := p.LB.DeleteRule(ctx, rule.ARN); err != nil {
t.Fatal(err)
}

rules, err = p.LB.DescribeRules(ctx, li.ARN)
if err != nil {
t.Fatal(err)
}

if len(rules) != 0 {
t.Errorf("expected 0 rules after deletion, got %d", len(rules))
}
}

func TestListenerRulesGCP(t *testing.T) {
ctx := context.Background()
p := NewGCP()

lb, err := p.LB.CreateLoadBalancer(ctx, lbdriver.LBConfig{
Name: "test-lb", Type: "application", Scheme: "internet-facing",
})
if err != nil {
t.Fatal(err)
}

tg, err := p.LB.CreateTargetGroup(ctx, lbdriver.TargetGroupConfig{
Name: "test-tg", Protocol: "HTTP", Port: 80, VPCID: "vpc-1",
})
if err != nil {
t.Fatal(err)
}

li, err := p.LB.CreateListener(ctx, lbdriver.ListenerConfig{
LBARN: lb.ARN, Protocol: "HTTP", Port: 80, TargetGroupARN: tg.ARN,
})
if err != nil {
t.Fatal(err)
}

rule, err := p.LB.CreateRule(ctx, lbdriver.RuleConfig{
ListenerARN: li.ARN,
Priority: 10,
Conditions: []lbdriver.RuleCondition{{Field: "path-pattern", Values: []string{"/api/*"}}},
Actions: []lbdriver.RuleAction{{Type: "forward", TargetGroupARN: tg.ARN}},
})
if err != nil {
t.Fatal(err)
}

if rule.ARN == "" {
t.Error("expected non-empty rule ARN")
}

rules, err := p.LB.DescribeRules(ctx, li.ARN)
if err != nil {
t.Fatal(err)
}

if len(rules) != 1 {
t.Errorf("expected 1 rule, got %d", len(rules))
}

if err := p.LB.DeleteRule(ctx, rule.ARN); err != nil {
t.Fatal(err)
}

rules, err = p.LB.DescribeRules(ctx, li.ARN)
if err != nil {
t.Fatal(err)
}

if len(rules) != 0 {
t.Errorf("expected 0 rules after deletion, got %d", len(rules))
}
}
55 changes: 55 additions & 0 deletions loadbalancer/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,52 @@ type ListenerInfo struct {
TargetGroupARN string
}

// RuleCondition describes a condition for a listener rule (e.g., path-pattern or host-header).
type RuleCondition struct {
Field string // "path-pattern" or "host-header"
Values []string
}

// RuleAction describes an action for a listener rule (e.g., forward to a target group).
type RuleAction struct {
Type string // "forward"
TargetGroupARN string
}

// RuleConfig describes a listener rule to create.
type RuleConfig struct {
ListenerARN string
Priority int
Conditions []RuleCondition
Actions []RuleAction
}

// RuleInfo describes a listener rule.
type RuleInfo struct {
ARN string
ListenerARN string
Priority int
Conditions []RuleCondition
Actions []RuleAction
IsDefault bool
}

// ModifyListenerInput describes modifications to apply to a listener.
type ModifyListenerInput struct {
ListenerARN string
Port int
Protocol string
DefaultActions []RuleAction
}

// LBAttributes describes configurable attributes of a load balancer.
type LBAttributes struct {
IdleTimeout int
DeletionProtection bool
AccessLogsEnabled bool
AccessLogsBucket string
}

// Target identifies a target (e.g., instance) in a target group.
type Target struct {
ID string
Expand Down Expand Up @@ -91,6 +137,15 @@ type LoadBalancer interface {
DeleteListener(ctx context.Context, arn string) error
DescribeListeners(ctx context.Context, lbARN string) ([]ListenerInfo, error)

CreateRule(ctx context.Context, config RuleConfig) (*RuleInfo, error)
DeleteRule(ctx context.Context, ruleARN string) error
DescribeRules(ctx context.Context, listenerARN string) ([]RuleInfo, error)

ModifyListener(ctx context.Context, input ModifyListenerInput) error

GetLBAttributes(ctx context.Context, lbARN string) (*LBAttributes, error)
PutLBAttributes(ctx context.Context, lbARN string, attrs LBAttributes) error

RegisterTargets(ctx context.Context, targetGroupARN string, targets []Target) error
DeregisterTargets(ctx context.Context, targetGroupARN string, targets []Target) error
DescribeTargetHealth(ctx context.Context, targetGroupARN string) ([]TargetHealth, error)
Expand Down
Loading