mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-22 20:26:44 +00:00
test: cover subscriber topology handler happy paths
Drives every vpc.* topology topic end to end against the mock OVN client and asserts the resulting rows: router, subnet switch and router port, logical switch port addresses and port-group membership, gateway router port, NAT rules and egress policies. Invalid CIDR, IP and MAC events are pinned to a failed reply envelope with no partial write. Moves the elbv2 dispatch table to an external test package. subscribers 53.5% -> 73.4%.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package gateway_elbv2
|
||||
package gateway_elbv2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||
"github.com/mulgadc/spinifex/spinifex/awserrors"
|
||||
gateway_elbv2 "github.com/mulgadc/spinifex/spinifex/gateway/elbv2"
|
||||
handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2"
|
||||
"github.com/mulgadc/spinifex/spinifex/testutil"
|
||||
"github.com/mulgadc/spinifex/spinifex/utils"
|
||||
@@ -22,6 +23,8 @@ const (
|
||||
testLBArn = "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/abc"
|
||||
testTGArn = "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/abc"
|
||||
testRuleArn = "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener-rule/app/my-alb/abc/def/ghi"
|
||||
testLsnrArn = "arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/lb/abc/def"
|
||||
testCrtArn = "arn:aws:acm:us-east-1:123456789012:certificate/aaaa-1111"
|
||||
)
|
||||
|
||||
// dispatchCase pins one gateway wrapper to the NATS subject it must publish on,
|
||||
@@ -73,25 +76,25 @@ func dispatchCases() []dispatchCase {
|
||||
createListenerIn := &elbv2.CreateListenerInput{
|
||||
LoadBalancerArn: aws.String(testLBArn), Port: aws.Int64(443), DefaultActions: actions,
|
||||
}
|
||||
createListenerOut := elbv2.CreateListenerOutput{Listeners: []*elbv2.Listener{{ListenerArn: aws.String(testListenerArn)}}}
|
||||
delListenerIn := &elbv2.DeleteListenerInput{ListenerArn: aws.String(testListenerArn)}
|
||||
modListenerIn := &elbv2.ModifyListenerInput{ListenerArn: aws.String(testListenerArn), Port: aws.Int64(8443)}
|
||||
createListenerOut := elbv2.CreateListenerOutput{Listeners: []*elbv2.Listener{{ListenerArn: aws.String(testLsnrArn)}}}
|
||||
delListenerIn := &elbv2.DeleteListenerInput{ListenerArn: aws.String(testLsnrArn)}
|
||||
modListenerIn := &elbv2.ModifyListenerInput{ListenerArn: aws.String(testLsnrArn), Port: aws.Int64(8443)}
|
||||
modListenerOut := elbv2.ModifyListenerOutput{Listeners: []*elbv2.Listener{{
|
||||
ListenerArn: aws.String(testListenerArn), Port: aws.Int64(8443),
|
||||
ListenerArn: aws.String(testLsnrArn), Port: aws.Int64(8443),
|
||||
}}}
|
||||
descListenersIn := &elbv2.DescribeListenersInput{LoadBalancerArn: aws.String(testLBArn)}
|
||||
descListenersOut := elbv2.DescribeListenersOutput{Listeners: []*elbv2.Listener{{ListenerArn: aws.String(testListenerArn)}}}
|
||||
descListenersOut := elbv2.DescribeListenersOutput{Listeners: []*elbv2.Listener{{ListenerArn: aws.String(testLsnrArn)}}}
|
||||
|
||||
conditions := []*elbv2.RuleCondition{{Field: aws.String("path-pattern"), Values: []*string{aws.String("/api/*")}}}
|
||||
createRuleIn := &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testListenerArn), Priority: aws.Int64(10),
|
||||
ListenerArn: aws.String(testLsnrArn), Priority: aws.Int64(10),
|
||||
Conditions: conditions, Actions: actions,
|
||||
}
|
||||
createRuleOut := elbv2.CreateRuleOutput{Rules: []*elbv2.Rule{{RuleArn: aws.String(testRuleArn), Priority: aws.String("10")}}}
|
||||
modRuleIn := &elbv2.ModifyRuleInput{RuleArn: aws.String(testRuleArn), Conditions: conditions}
|
||||
modRuleOut := elbv2.ModifyRuleOutput{Rules: []*elbv2.Rule{{RuleArn: aws.String(testRuleArn)}}}
|
||||
delRuleIn := &elbv2.DeleteRuleInput{RuleArn: aws.String(testRuleArn)}
|
||||
descRulesIn := &elbv2.DescribeRulesInput{ListenerArn: aws.String(testListenerArn)}
|
||||
descRulesIn := &elbv2.DescribeRulesInput{ListenerArn: aws.String(testLsnrArn)}
|
||||
descRulesOut := elbv2.DescribeRulesOutput{Rules: []*elbv2.Rule{{RuleArn: aws.String(testRuleArn)}}}
|
||||
prioIn := &elbv2.SetRulePrioritiesInput{RulePriorities: []*elbv2.RulePriorityPair{{
|
||||
RuleArn: aws.String(testRuleArn), Priority: aws.Int64(5),
|
||||
@@ -127,11 +130,11 @@ func dispatchCases() []dispatchCase {
|
||||
subnetsIn := &elbv2.SetSubnetsInput{LoadBalancerArn: aws.String(testLBArn), Subnets: []*string{aws.String("subnet-1")}}
|
||||
subnetsOut := elbv2.SetSubnetsOutput{AvailabilityZones: []*elbv2.AvailabilityZone{{SubnetId: aws.String("subnet-1")}}}
|
||||
|
||||
certs := []*elbv2.Certificate{{CertificateArn: aws.String(testCertArn)}}
|
||||
addCertIn := &elbv2.AddListenerCertificatesInput{ListenerArn: aws.String(testListenerArn), Certificates: certs}
|
||||
certs := []*elbv2.Certificate{{CertificateArn: aws.String(testCrtArn)}}
|
||||
addCertIn := &elbv2.AddListenerCertificatesInput{ListenerArn: aws.String(testLsnrArn), Certificates: certs}
|
||||
addCertOut := elbv2.AddListenerCertificatesOutput{Certificates: certs}
|
||||
removeCertIn := &elbv2.RemoveListenerCertificatesInput{ListenerArn: aws.String(testListenerArn), Certificates: certs}
|
||||
descCertIn := &elbv2.DescribeListenerCertificatesInput{ListenerArn: aws.String(testListenerArn)}
|
||||
removeCertIn := &elbv2.RemoveListenerCertificatesInput{ListenerArn: aws.String(testLsnrArn), Certificates: certs}
|
||||
descCertIn := &elbv2.DescribeListenerCertificatesInput{ListenerArn: aws.String(testLsnrArn)}
|
||||
descCertOut := elbv2.DescribeListenerCertificatesOutput{Certificates: certs}
|
||||
sslIn := &elbv2.DescribeSSLPoliciesInput{Names: []*string{aws.String("ELBSecurityPolicy-2016-08")}}
|
||||
sslOut := elbv2.DescribeSSLPoliciesOutput{SslPolicies: []*elbv2.SslPolicy{{Name: aws.String("ELBSecurityPolicy-2016-08")}}}
|
||||
@@ -153,210 +156,210 @@ func dispatchCases() []dispatchCase {
|
||||
name: "CreateLoadBalancer", subject: "elbv2.CreateLoadBalancer",
|
||||
input: lbIn, reply: lbOut, want: lbOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return CreateLoadBalancer(ctx, lbIn, nc, testAccountID)
|
||||
return gateway_elbv2.CreateLoadBalancer(ctx, lbIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DeleteLoadBalancer", subject: "elbv2.DeleteLoadBalancer",
|
||||
input: delLBIn, reply: elbv2.DeleteLoadBalancerOutput{}, want: elbv2.DeleteLoadBalancerOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DeleteLoadBalancer(ctx, delLBIn, nc, testAccountID)
|
||||
return gateway_elbv2.DeleteLoadBalancer(ctx, delLBIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeLoadBalancers", subject: "elbv2.DescribeLoadBalancers",
|
||||
input: descLBIn, reply: descLBOut, want: descLBOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeLoadBalancers(ctx, descLBIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeLoadBalancers(ctx, descLBIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CreateTargetGroup", subject: "elbv2.CreateTargetGroup",
|
||||
input: tgIn, reply: tgOut, want: tgOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return CreateTargetGroup(ctx, tgIn, nc, testAccountID)
|
||||
return gateway_elbv2.CreateTargetGroup(ctx, tgIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ModifyTargetGroup", subject: "elbv2.ModifyTargetGroup",
|
||||
input: modTGIn, reply: modTGOut, want: modTGOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return ModifyTargetGroup(ctx, modTGIn, nc, testAccountID)
|
||||
return gateway_elbv2.ModifyTargetGroup(ctx, modTGIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DeleteTargetGroup", subject: "elbv2.DeleteTargetGroup",
|
||||
input: delTGIn, reply: elbv2.DeleteTargetGroupOutput{}, want: elbv2.DeleteTargetGroupOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DeleteTargetGroup(ctx, delTGIn, nc, testAccountID)
|
||||
return gateway_elbv2.DeleteTargetGroup(ctx, delTGIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeTargetGroups", subject: "elbv2.DescribeTargetGroups",
|
||||
input: descTGIn, reply: descTGOut, want: descTGOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeTargetGroups(ctx, descTGIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeTargetGroups(ctx, descTGIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "RegisterTargets", subject: "elbv2.RegisterTargets",
|
||||
input: regIn, reply: elbv2.RegisterTargetsOutput{}, want: elbv2.RegisterTargetsOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return RegisterTargets(ctx, regIn, nc, testAccountID)
|
||||
return gateway_elbv2.RegisterTargets(ctx, regIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DeregisterTargets", subject: "elbv2.DeregisterTargets",
|
||||
input: deregIn, reply: elbv2.DeregisterTargetsOutput{}, want: elbv2.DeregisterTargetsOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DeregisterTargets(ctx, deregIn, nc, testAccountID)
|
||||
return gateway_elbv2.DeregisterTargets(ctx, deregIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeTargetHealth", subject: "elbv2.DescribeTargetHealth",
|
||||
input: healthIn, reply: healthOut, want: healthOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeTargetHealth(ctx, healthIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeTargetHealth(ctx, healthIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CreateListener", subject: "elbv2.CreateListener",
|
||||
input: createListenerIn, reply: createListenerOut, want: createListenerOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return CreateListener(ctx, createListenerIn, nc, testAccountID)
|
||||
return gateway_elbv2.CreateListener(ctx, createListenerIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DeleteListener", subject: "elbv2.DeleteListener",
|
||||
input: delListenerIn, reply: elbv2.DeleteListenerOutput{}, want: elbv2.DeleteListenerOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DeleteListener(ctx, delListenerIn, nc, testAccountID)
|
||||
return gateway_elbv2.DeleteListener(ctx, delListenerIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ModifyListener", subject: "elbv2.ModifyListener",
|
||||
input: modListenerIn, reply: modListenerOut, want: modListenerOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return ModifyListener(ctx, modListenerIn, nc, testAccountID)
|
||||
return gateway_elbv2.ModifyListener(ctx, modListenerIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeListeners", subject: "elbv2.DescribeListeners",
|
||||
input: descListenersIn, reply: descListenersOut, want: descListenersOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeListeners(ctx, descListenersIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeListeners(ctx, descListenersIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CreateRule", subject: "elbv2.CreateRule",
|
||||
input: createRuleIn, reply: createRuleOut, want: createRuleOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return CreateRule(ctx, createRuleIn, nc, testAccountID)
|
||||
return gateway_elbv2.CreateRule(ctx, createRuleIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ModifyRule", subject: "elbv2.ModifyRule",
|
||||
input: modRuleIn, reply: modRuleOut, want: modRuleOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return ModifyRule(ctx, modRuleIn, nc, testAccountID)
|
||||
return gateway_elbv2.ModifyRule(ctx, modRuleIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DeleteRule", subject: "elbv2.DeleteRule",
|
||||
input: delRuleIn, reply: elbv2.DeleteRuleOutput{}, want: elbv2.DeleteRuleOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DeleteRule(ctx, delRuleIn, nc, testAccountID)
|
||||
return gateway_elbv2.DeleteRule(ctx, delRuleIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeRules", subject: "elbv2.DescribeRules",
|
||||
input: descRulesIn, reply: descRulesOut, want: descRulesOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeRules(ctx, descRulesIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeRules(ctx, descRulesIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetRulePriorities", subject: "elbv2.SetRulePriorities",
|
||||
input: prioIn, reply: prioOut, want: prioOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return SetRulePriorities(ctx, prioIn, nc, testAccountID)
|
||||
return gateway_elbv2.SetRulePriorities(ctx, prioIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeTags", subject: "elbv2.DescribeTags",
|
||||
input: descTagsIn, reply: descTagsOut, want: descTagsOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeTags(ctx, descTagsIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeTags(ctx, descTagsIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AddTags", subject: "elbv2.AddTags",
|
||||
input: addTagsIn, reply: elbv2.AddTagsOutput{}, want: elbv2.AddTagsOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return AddTags(ctx, addTagsIn, nc, testAccountID)
|
||||
return gateway_elbv2.AddTags(ctx, addTagsIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "RemoveTags", subject: "elbv2.RemoveTags",
|
||||
input: removeTagsIn, reply: elbv2.RemoveTagsOutput{}, want: elbv2.RemoveTagsOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return RemoveTags(ctx, removeTagsIn, nc, testAccountID)
|
||||
return gateway_elbv2.RemoveTags(ctx, removeTagsIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ModifyTargetGroupAttributes", subject: "elbv2.ModifyTargetGroupAttributes",
|
||||
input: modTGAttrIn, reply: modTGAttrOut, want: modTGAttrOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return ModifyTargetGroupAttributes(ctx, modTGAttrIn, nc, testAccountID)
|
||||
return gateway_elbv2.ModifyTargetGroupAttributes(ctx, modTGAttrIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeTargetGroupAttributes", subject: "elbv2.DescribeTargetGroupAttributes",
|
||||
input: descTGAttrIn, reply: descTGAttrOut, want: descTGAttrOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeTargetGroupAttributes(ctx, descTGAttrIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeTargetGroupAttributes(ctx, descTGAttrIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ModifyLoadBalancerAttributes", subject: "elbv2.ModifyLoadBalancerAttributes",
|
||||
input: modLBAttrIn, reply: modLBAttrOut, want: modLBAttrOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return ModifyLoadBalancerAttributes(ctx, modLBAttrIn, nc, testAccountID)
|
||||
return gateway_elbv2.ModifyLoadBalancerAttributes(ctx, modLBAttrIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeLoadBalancerAttributes", subject: "elbv2.DescribeLoadBalancerAttributes",
|
||||
input: descLBAttrIn, reply: descLBAttrOut, want: descLBAttrOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeLoadBalancerAttributes(ctx, descLBAttrIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeLoadBalancerAttributes(ctx, descLBAttrIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetSecurityGroups", subject: "elbv2.SetSecurityGroups",
|
||||
input: sgIn, reply: sgOut, want: sgOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return SetSecurityGroups(ctx, sgIn, nc, testAccountID)
|
||||
return gateway_elbv2.SetSecurityGroups(ctx, sgIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetIpAddressType", subject: "elbv2.SetIpAddressType",
|
||||
input: ipTypeIn, reply: ipTypeOut, want: ipTypeOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return SetIpAddressType(ctx, ipTypeIn, nc, testAccountID)
|
||||
return gateway_elbv2.SetIpAddressType(ctx, ipTypeIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetSubnets", subject: "elbv2.SetSubnets",
|
||||
input: subnetsIn, reply: subnetsOut, want: subnetsOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return SetSubnets(ctx, subnetsIn, nc, testAccountID)
|
||||
return gateway_elbv2.SetSubnets(ctx, subnetsIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AddListenerCertificates", subject: "elbv2.AddListenerCertificates",
|
||||
input: addCertIn, reply: addCertOut, want: addCertOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return AddListenerCertificates(ctx, addCertIn, nc, testAccountID)
|
||||
return gateway_elbv2.AddListenerCertificates(ctx, addCertIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -364,35 +367,35 @@ func dispatchCases() []dispatchCase {
|
||||
input: removeCertIn, reply: elbv2.RemoveListenerCertificatesOutput{},
|
||||
want: elbv2.RemoveListenerCertificatesOutput{},
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return RemoveListenerCertificates(ctx, removeCertIn, nc, testAccountID)
|
||||
return gateway_elbv2.RemoveListenerCertificates(ctx, removeCertIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeListenerCertificates", subject: "elbv2.DescribeListenerCertificates",
|
||||
input: descCertIn, reply: descCertOut, want: descCertOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeListenerCertificates(ctx, descCertIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeListenerCertificates(ctx, descCertIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DescribeSSLPolicies", subject: "elbv2.DescribeSSLPolicies",
|
||||
input: sslIn, reply: sslOut, want: sslOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return DescribeSSLPolicies(ctx, sslIn, nc, testAccountID)
|
||||
return gateway_elbv2.DescribeSSLPolicies(ctx, sslIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LBAgentHeartbeat", subject: "elbv2.LBAgentHeartbeat",
|
||||
input: heartbeatIn, reply: heartbeatOut, want: heartbeatOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return LBAgentHeartbeat(ctx, heartbeatIn, nc, testAccountID)
|
||||
return gateway_elbv2.LBAgentHeartbeat(ctx, heartbeatIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetLBConfig", subject: "elbv2.GetLBConfig",
|
||||
input: lbConfigIn, reply: lbConfigOut, want: lbConfigOut,
|
||||
call: func(ctx context.Context, nc *nats.Conn) (any, error) {
|
||||
return GetLBConfig(ctx, lbConfigIn, nc, testAccountID)
|
||||
return gateway_elbv2.GetLBConfig(ctx, lbConfigIn, nc, testAccountID)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -452,136 +455,136 @@ func TestGatewayDispatch_ValidationGuards(t *testing.T) {
|
||||
call func() error
|
||||
}{
|
||||
{"CreateRule/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := CreateRule(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.CreateRule(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"CreateRule/MissingListenerArn", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := CreateRule(context.Background(), &elbv2.CreateRuleInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.CreateRule(context.Background(), &elbv2.CreateRuleInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"CreateRule/MissingPriority", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := CreateRule(context.Background(), &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testListenerArn),
|
||||
_, err := gateway_elbv2.CreateRule(context.Background(), &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testLsnrArn),
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"CreateRule/MissingConditions", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := CreateRule(context.Background(), &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testListenerArn), Priority: aws.Int64(1),
|
||||
_, err := gateway_elbv2.CreateRule(context.Background(), &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testLsnrArn), Priority: aws.Int64(1),
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"CreateRule/MissingActions", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := CreateRule(context.Background(), &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testListenerArn), Priority: aws.Int64(1),
|
||||
_, err := gateway_elbv2.CreateRule(context.Background(), &elbv2.CreateRuleInput{
|
||||
ListenerArn: aws.String(testLsnrArn), Priority: aws.Int64(1),
|
||||
Conditions: []*elbv2.RuleCondition{{Field: aws.String("path-pattern")}},
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"ModifyRule/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := ModifyRule(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.ModifyRule(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"ModifyRule/MissingRuleArn", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := ModifyRule(context.Background(), &elbv2.ModifyRuleInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.ModifyRule(context.Background(), &elbv2.ModifyRuleInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
// Either conditions or actions satisfies the guard; neither does not.
|
||||
{"ModifyRule/NoConditionsOrActions", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := ModifyRule(context.Background(), &elbv2.ModifyRuleInput{
|
||||
_, err := gateway_elbv2.ModifyRule(context.Background(), &elbv2.ModifyRuleInput{
|
||||
RuleArn: aws.String(testRuleArn),
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"DeleteRule/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := DeleteRule(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.DeleteRule(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"DeleteRule/MissingRuleArn", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := DeleteRule(context.Background(), &elbv2.DeleteRuleInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.DeleteRule(context.Background(), &elbv2.DeleteRuleInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"DescribeRules/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := DescribeRules(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.DescribeRules(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"DescribeRules/NeitherListenerNorRules", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := DescribeRules(context.Background(), &elbv2.DescribeRulesInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.DescribeRules(context.Background(), &elbv2.DescribeRulesInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetRulePriorities/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := SetRulePriorities(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.SetRulePriorities(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetRulePriorities/MissingPriorities", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetRulePriorities(context.Background(), &elbv2.SetRulePrioritiesInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.SetRulePriorities(context.Background(), &elbv2.SetRulePrioritiesInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"AddTags/NilInput", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := AddTags(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.AddTags(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"AddTags/MissingTags", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := AddTags(context.Background(), &elbv2.AddTagsInput{
|
||||
_, err := gateway_elbv2.AddTags(context.Background(), &elbv2.AddTagsInput{
|
||||
ResourceArns: []*string{aws.String(testLBArn)},
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"RemoveTags/MissingTagKeys", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := RemoveTags(context.Background(), &elbv2.RemoveTagsInput{
|
||||
_, err := gateway_elbv2.RemoveTags(context.Background(), &elbv2.RemoveTagsInput{
|
||||
ResourceArns: []*string{aws.String(testLBArn)},
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"DescribeTags/MissingResourceArns", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := DescribeTags(context.Background(), &elbv2.DescribeTagsInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.DescribeTags(context.Background(), &elbv2.DescribeTagsInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetSecurityGroups/MissingLBArn", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetSecurityGroups(context.Background(), &elbv2.SetSecurityGroupsInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.SetSecurityGroups(context.Background(), &elbv2.SetSecurityGroupsInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetSecurityGroups/MissingGroups", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetSecurityGroups(context.Background(), &elbv2.SetSecurityGroupsInput{
|
||||
_, err := gateway_elbv2.SetSecurityGroups(context.Background(), &elbv2.SetSecurityGroupsInput{
|
||||
LoadBalancerArn: aws.String(testLBArn),
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetIpAddressType/MissingLBArn", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetIpAddressType(context.Background(), &elbv2.SetIpAddressTypeInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.SetIpAddressType(context.Background(), &elbv2.SetIpAddressTypeInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetIpAddressType/MissingType", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetIpAddressType(context.Background(), &elbv2.SetIpAddressTypeInput{
|
||||
_, err := gateway_elbv2.SetIpAddressType(context.Background(), &elbv2.SetIpAddressTypeInput{
|
||||
LoadBalancerArn: aws.String(testLBArn),
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"SetSubnets/MissingLBArn", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetSubnets(context.Background(), &elbv2.SetSubnetsInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.SetSubnets(context.Background(), &elbv2.SetSubnetsInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
// SubnetMappings is the accepted alternative to Subnets; neither fails.
|
||||
{"SetSubnets/NoSubnetsOrMappings", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := SetSubnets(context.Background(), &elbv2.SetSubnetsInput{
|
||||
_, err := gateway_elbv2.SetSubnets(context.Background(), &elbv2.SetSubnetsInput{
|
||||
LoadBalancerArn: aws.String(testLBArn),
|
||||
}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"LBAgentHeartbeat/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := LBAgentHeartbeat(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.LBAgentHeartbeat(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"LBAgentHeartbeat/MissingLBID", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := LBAgentHeartbeat(context.Background(), &handlers_elbv2.LBAgentHeartbeatInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.LBAgentHeartbeat(context.Background(), &handlers_elbv2.LBAgentHeartbeatInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"GetLBConfig/NilInput", awserrors.ErrorInvalidParameterValue, func() error {
|
||||
_, err := GetLBConfig(context.Background(), nil, nil, testAccountID)
|
||||
_, err := gateway_elbv2.GetLBConfig(context.Background(), nil, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
{"GetLBConfig/MissingLBID", awserrors.ErrorMissingParameter, func() error {
|
||||
_, err := GetLBConfig(context.Background(), &handlers_elbv2.GetLBConfigInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.GetLBConfig(context.Background(), &handlers_elbv2.GetLBConfigInput{}, nil, testAccountID)
|
||||
return err
|
||||
}},
|
||||
}
|
||||
@@ -600,7 +603,7 @@ func TestGatewayDispatch_ErrorEnvelopePropagates(t *testing.T) {
|
||||
serveOnce(t, nc, "elbv2.DeleteLoadBalancer",
|
||||
utils.GenerateErrorPayload(awserrors.ErrorELBv2LoadBalancerNotFound))
|
||||
|
||||
_, err := DeleteLoadBalancer(context.Background(),
|
||||
_, err := gateway_elbv2.DeleteLoadBalancer(context.Background(),
|
||||
&elbv2.DeleteLoadBalancerInput{LoadBalancerArn: aws.String(testLBArn)}, nc, testAccountID)
|
||||
require.EqualError(t, err, awserrors.ErrorELBv2LoadBalancerNotFound)
|
||||
}
|
||||
@@ -610,12 +613,12 @@ func TestGatewayDispatch_ErrorEnvelopePropagates(t *testing.T) {
|
||||
func TestGatewayDispatch_NoResponder(t *testing.T) {
|
||||
_, nc := testutil.StartTestNATS(t)
|
||||
|
||||
_, err := DescribeLoadBalancers(context.Background(), &elbv2.DescribeLoadBalancersInput{}, nc, testAccountID)
|
||||
_, err := gateway_elbv2.DescribeLoadBalancers(context.Background(), &elbv2.DescribeLoadBalancersInput{}, nc, testAccountID)
|
||||
require.ErrorIs(t, err, nats.ErrNoResponders)
|
||||
}
|
||||
|
||||
// A nil connection is refused before any request is attempted.
|
||||
func TestGatewayDispatch_NilConnection(t *testing.T) {
|
||||
_, err := DescribeLoadBalancers(context.Background(), &elbv2.DescribeLoadBalancersInput{}, nil, testAccountID)
|
||||
_, err := gateway_elbv2.DescribeLoadBalancers(context.Background(), &elbv2.DescribeLoadBalancersInput{}, nil, testAccountID)
|
||||
require.ErrorIs(t, err, utils.ErrClusterUnavailable)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
//test:in-package — the handlers under test are unexported methods on
|
||||
// Subscriber, and the tests reuse newTestSubscriber and the respondResponse
|
||||
// envelope, both of which are package-internal.
|
||||
|
||||
package subscribers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mulgadc/spinifex/spinifex/network/external"
|
||||
"github.com/mulgadc/spinifex/spinifex/network/ovn/mock"
|
||||
"github.com/mulgadc/spinifex/spinifex/network/ovn/nbdb"
|
||||
"github.com/mulgadc/spinifex/spinifex/network/policy"
|
||||
"github.com/mulgadc/spinifex/spinifex/network/topology"
|
||||
"github.com/mulgadc/spinifex/spinifex/testutil"
|
||||
"github.com/mulgadc/spinifex/spinifex/types"
|
||||
)
|
||||
|
||||
// runningSubscriber wires a subscriber to a live NATS server with every topic
|
||||
// registered, so tests drive handlers the way vpcd does.
|
||||
func runningSubscriber(t *testing.T) (*nats.Conn, *Subscriber, *mock.Client) {
|
||||
t.Helper()
|
||||
_, nc := testutil.StartTestNATS(t)
|
||||
sub, m := newTestSubscriber(t)
|
||||
subs, err := sub.Subscribe(nc)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
for _, s := range subs {
|
||||
_ = s.Unsubscribe()
|
||||
}
|
||||
})
|
||||
return nc, sub, m
|
||||
}
|
||||
|
||||
// requestOK publishes evt and requires the handler's reply envelope to report
|
||||
// success, so a handler that errors fails here rather than in a state assertion.
|
||||
func requestOK(t *testing.T, nc *nats.Conn, topic string, evt any) {
|
||||
t.Helper()
|
||||
data, err := json.Marshal(evt)
|
||||
require.NoError(t, err)
|
||||
resp, err := nc.Request(topic, data, requestTimeout)
|
||||
require.NoError(t, err)
|
||||
var env respondResponse
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &env))
|
||||
require.True(t, env.Success, "%s: %s", topic, env.Error)
|
||||
}
|
||||
|
||||
// requestFails is requestOK's negative: the handler must reject the event and
|
||||
// say so in the envelope.
|
||||
func requestFails(t *testing.T, nc *nats.Conn, topic string, evt any) string {
|
||||
t.Helper()
|
||||
data, err := json.Marshal(evt)
|
||||
require.NoError(t, err)
|
||||
resp, err := nc.Request(topic, data, requestTimeout)
|
||||
require.NoError(t, err)
|
||||
var env respondResponse
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &env))
|
||||
require.False(t, env.Success, "%s must reject the event", topic)
|
||||
return env.Error
|
||||
}
|
||||
|
||||
// The mock reports a missing row as an error rather than a nil result, so
|
||||
// absence is asserted against the list APIs.
|
||||
|
||||
func routerExists(t *testing.T, m *mock.Client, name string) bool {
|
||||
t.Helper()
|
||||
rows, err := m.ListLogicalRouters(context.Background())
|
||||
require.NoError(t, err)
|
||||
for _, r := range rows {
|
||||
if r.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func switchExists(t *testing.T, m *mock.Client, name string) bool {
|
||||
t.Helper()
|
||||
rows, err := m.ListLogicalSwitches(context.Background())
|
||||
require.NoError(t, err)
|
||||
for _, s := range rows {
|
||||
if s.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func switchPortExists(t *testing.T, m *mock.Client, name string) bool {
|
||||
t.Helper()
|
||||
rows, err := m.ListLogicalSwitchPorts(context.Background())
|
||||
require.NoError(t, err)
|
||||
for _, p := range rows {
|
||||
if p.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func routerPortExists(t *testing.T, m *mock.Client, name string) bool {
|
||||
t.Helper()
|
||||
rows, err := m.ListLogicalRouterPorts(context.Background())
|
||||
require.NoError(t, err)
|
||||
for _, p := range rows {
|
||||
if p.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// seedVPCWithIGW creates the router and attaches an IGW, the precondition for
|
||||
// every egress-policy handler.
|
||||
func seedVPCWithIGW(t *testing.T, sub *Subscriber, m *mock.Client, vpcID string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
require.NoError(t, m.CreateLogicalRouter(ctx, &nbdb.LogicalRouter{
|
||||
Name: topology.VPCRouter(vpcID),
|
||||
ExternalIDs: map[string]string{"spinifex:vpc_id": vpcID},
|
||||
}))
|
||||
require.NoError(t, sub.igw.AttachIGW(ctx, external.IGWSpec{VPCID: vpcID, InternetGatewayID: "igw-1"}))
|
||||
}
|
||||
|
||||
func TestHandleVPC_CreateAndDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicVPCCreate, VPCEvent{VpcId: "vpc-1", CidrBlock: "10.0.0.0/16", VNI: 7})
|
||||
|
||||
lr, err := m.GetLogicalRouter(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lr, "vpc.create must create the VPC logical router")
|
||||
assert.Equal(t, "vpc-1", lr.ExternalIDs["spinifex:vpc_id"])
|
||||
|
||||
requestOK(t, nc, TopicVPCDelete, VPCEvent{VpcId: "vpc-1"})
|
||||
|
||||
assert.False(t, routerExists(t, m, topology.VPCRouter("vpc-1")),
|
||||
"vpc.delete must remove the VPC logical router")
|
||||
}
|
||||
|
||||
// A CIDR that will not parse must be rejected before any OVN write.
|
||||
func TestHandleVPCCreate_InvalidCIDR(t *testing.T) {
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestFails(t, nc, TopicVPCCreate, VPCEvent{VpcId: "vpc-1", CidrBlock: "not-a-cidr"})
|
||||
|
||||
assert.False(t, routerExists(t, m, topology.VPCRouter("vpc-1")),
|
||||
"invalid CIDR must not create a router")
|
||||
}
|
||||
|
||||
// vpc.create and vpc.create-subnet have no ordering guarantee, so the subnet
|
||||
// handler pre-ensures the router itself.
|
||||
func TestHandleSubnetCreate_PreEnsuresVPCRouter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicSubnetCreate, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/24",
|
||||
})
|
||||
|
||||
lr, err := m.GetLogicalRouter(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lr, "subnet create must pre-ensure the VPC router")
|
||||
|
||||
require.True(t, switchExists(t, m, topology.SubnetSwitch("subnet-1")),
|
||||
"subnet create must create the subnet switch")
|
||||
|
||||
lrp, err := m.GetLogicalRouterPort(ctx, topology.SubnetRouterPort("subnet-1"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lrp, "subnet create must attach the subnet to its router")
|
||||
assert.Contains(t, lrp.Networks, "10.0.1.1/24", "router port takes the subnet's first usable address")
|
||||
|
||||
requestOK(t, nc, TopicSubnetDelete, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/24",
|
||||
})
|
||||
|
||||
assert.False(t, switchExists(t, m, topology.SubnetSwitch("subnet-1")),
|
||||
"subnet delete must remove the subnet switch")
|
||||
}
|
||||
|
||||
func TestHandleSubnetCreate_InvalidCIDR(t *testing.T) {
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestFails(t, nc, TopicSubnetCreate, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/33",
|
||||
})
|
||||
|
||||
assert.False(t, switchExists(t, m, topology.SubnetSwitch("subnet-1")))
|
||||
}
|
||||
|
||||
// delete-subnet tolerates a missing CIDR: the spec is keyed by ID, and the
|
||||
// CIDR is only used to release the DHCP options row.
|
||||
func TestHandleSubnetDelete_WithoutCIDR(t *testing.T) {
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicSubnetCreate, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/24",
|
||||
})
|
||||
requestOK(t, nc, TopicSubnetDelete, SubnetEvent{SubnetId: "subnet-1", VpcId: "vpc-1"})
|
||||
|
||||
assert.False(t, switchExists(t, m, topology.SubnetSwitch("subnet-1")))
|
||||
}
|
||||
|
||||
func TestHandlePort_CreateAndDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicSubnetCreate, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/24",
|
||||
})
|
||||
require.NoError(t, sub.topology.EnsureSGPortGroup(ctx, "sg-1"))
|
||||
|
||||
requestOK(t, nc, TopicCreatePort, PortEvent{
|
||||
NetworkInterfaceId: "eni-1", SubnetId: "subnet-1", VpcId: "vpc-1",
|
||||
PrivateIpAddress: "10.0.1.10", MacAddress: "02:00:00:00:00:01",
|
||||
SecurityGroupIds: []string{"sg-1"},
|
||||
})
|
||||
|
||||
lsp, err := m.GetLogicalSwitchPort(ctx, topology.Port("eni-1"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, lsp, "create-port must create the logical switch port")
|
||||
assert.Contains(t, lsp.Addresses, "02:00:00:00:00:01 10.0.1.10")
|
||||
|
||||
pgs, err := m.ListPortGroupsForPort(ctx, topology.Port("eni-1"))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, pgs, topology.SecurityGroupPortGroup("sg-1"),
|
||||
"create-port must join the port to its security group")
|
||||
|
||||
requestOK(t, nc, TopicDeletePort, PortEvent{
|
||||
NetworkInterfaceId: "eni-1", SubnetId: "subnet-1", VpcId: "vpc-1",
|
||||
})
|
||||
|
||||
assert.False(t, switchPortExists(t, m, topology.Port("eni-1")),
|
||||
"delete-port must remove the logical switch port")
|
||||
}
|
||||
|
||||
// Address fields are parsed before the OVN write, so a malformed IP or MAC is
|
||||
// reported rather than half-applied.
|
||||
func TestHandleCreatePort_MalformedAddresses(t *testing.T) {
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicSubnetCreate, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/24",
|
||||
})
|
||||
|
||||
requestFails(t, nc, TopicCreatePort, PortEvent{
|
||||
NetworkInterfaceId: "eni-bad-ip", SubnetId: "subnet-1", VpcId: "vpc-1",
|
||||
PrivateIpAddress: "999.0.0.1", MacAddress: "02:00:00:00:00:01",
|
||||
})
|
||||
requestFails(t, nc, TopicCreatePort, PortEvent{
|
||||
NetworkInterfaceId: "eni-bad-mac", SubnetId: "subnet-1", VpcId: "vpc-1",
|
||||
PrivateIpAddress: "10.0.1.10", MacAddress: "not-a-mac",
|
||||
})
|
||||
|
||||
for _, name := range []string{"eni-bad-ip", "eni-bad-mac"} {
|
||||
assert.False(t, switchPortExists(t, m, topology.Port(name)), "%s must not be created", name)
|
||||
}
|
||||
}
|
||||
|
||||
// update-port-sgs is declarative: the handler passes the full desired list and
|
||||
// the manager diffs it, so a shrinking list drops memberships.
|
||||
func TestHandleUpdatePortSGs_ReplacesMemberships(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicSubnetCreate, SubnetEvent{
|
||||
SubnetId: "subnet-1", VpcId: "vpc-1", CidrBlock: "10.0.1.0/24",
|
||||
})
|
||||
for _, sg := range []string{"sg-1", "sg-2"} {
|
||||
require.NoError(t, sub.topology.EnsureSGPortGroup(ctx, sg))
|
||||
}
|
||||
requestOK(t, nc, TopicCreatePort, PortEvent{
|
||||
NetworkInterfaceId: "eni-1", SubnetId: "subnet-1", VpcId: "vpc-1",
|
||||
PrivateIpAddress: "10.0.1.10", MacAddress: "02:00:00:00:00:01",
|
||||
SecurityGroupIds: []string{"sg-1"},
|
||||
})
|
||||
|
||||
requestOK(t, nc, TopicUpdatePortSGs, UpdatePortSGsEvent{
|
||||
NetworkInterfaceId: "eni-1", PrivateIpAddress: "10.0.1.10",
|
||||
SecurityGroupIds: []string{"sg-2"},
|
||||
})
|
||||
|
||||
pgs, err := m.ListPortGroupsForPort(ctx, topology.Port("eni-1"))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, pgs, topology.SecurityGroupPortGroup("sg-2"))
|
||||
assert.NotContains(t, pgs, topology.SecurityGroupPortGroup("sg-1"),
|
||||
"a group absent from the event must be removed")
|
||||
}
|
||||
|
||||
func TestHandleIGW_AttachAndDetach(t *testing.T) {
|
||||
nc, _, m := runningSubscriber(t)
|
||||
|
||||
requestOK(t, nc, TopicVPCCreate, VPCEvent{VpcId: "vpc-1", CidrBlock: "10.0.0.0/16"})
|
||||
requestOK(t, nc, TopicIGWAttach, types.IGWEvent{VpcId: "vpc-1", InternetGatewayId: "igw-1"})
|
||||
|
||||
require.True(t, routerPortExists(t, m, topology.GatewayRouterPort("vpc-1")),
|
||||
"igw-attach must create the gateway router port")
|
||||
|
||||
requestOK(t, nc, TopicIGWDetach, types.IGWEvent{VpcId: "vpc-1", InternetGatewayId: "igw-1"})
|
||||
|
||||
assert.False(t, routerPortExists(t, m, topology.GatewayRouterPort("vpc-1")),
|
||||
"igw-detach must remove the gateway router port")
|
||||
}
|
||||
|
||||
func TestHandleNAT_AddAndDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
seedVPCWithIGW(t, sub, m, "vpc-1")
|
||||
|
||||
evt := NATEvent{
|
||||
VpcId: "vpc-1", ExternalIP: "192.168.1.70", LogicalIP: "10.0.1.10",
|
||||
PortName: topology.Port("eni-1"), MAC: "02:00:00:00:00:01",
|
||||
}
|
||||
requestOK(t, nc, TopicAddNAT, evt)
|
||||
|
||||
nat, err := m.FindNATByExternalIP(ctx, "dnat_and_snat", "192.168.1.70")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, nat, "add-nat must install a dnat_and_snat rule")
|
||||
assert.Equal(t, "10.0.1.10", nat.LogicalIP)
|
||||
|
||||
requestOK(t, nc, TopicDeleteNAT, evt)
|
||||
|
||||
nat, err = m.FindNATByExternalIP(ctx, "dnat_and_snat", "192.168.1.70")
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, nat, "delete-nat must remove the rule")
|
||||
}
|
||||
|
||||
// delete-igw-route must remove exactly the policy add-igw-route installed.
|
||||
func TestHandleIGWRoute_DeleteRemovesPolicy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
seedVPCWithIGW(t, sub, m, "vpc-1")
|
||||
|
||||
evt := IGWRouteEvent{
|
||||
VpcId: "vpc-1", SubnetId: "subnet-pub",
|
||||
DestinationCidr: "0.0.0.0/0", InternetGatewayId: "igw-1",
|
||||
}
|
||||
requestOK(t, nc, TopicAddIGWRoute, evt)
|
||||
|
||||
policies, err := m.ListLogicalRouterPolicies(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
|
||||
requestOK(t, nc, TopicDeleteIGWRoute, evt)
|
||||
|
||||
policies, err = m.ListLogicalRouterPolicies(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, policies, "delete-igw-route must remove the egress policy")
|
||||
}
|
||||
|
||||
func TestHandleDeleteIGWRoute_InvalidCIDR(t *testing.T) {
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
seedVPCWithIGW(t, sub, m, "vpc-1")
|
||||
|
||||
requestFails(t, nc, TopicDeleteIGWRoute, IGWRouteEvent{
|
||||
VpcId: "vpc-1", SubnetId: "subnet-pub", DestinationCidr: "not-a-cidr",
|
||||
})
|
||||
}
|
||||
|
||||
// Gating installs a DROP above both reroute priorities; ungating removes it.
|
||||
func TestHandleSubnetEgress_GateAndUngate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
seedVPCWithIGW(t, sub, m, "vpc-1")
|
||||
|
||||
gate := SubnetEgressGateEvent{VpcId: "vpc-1", SubnetId: "subnet-1", DestinationCidr: "0.0.0.0/0"}
|
||||
requestOK(t, nc, TopicGateSubnetEgress, gate)
|
||||
|
||||
policies, err := m.ListLogicalRouterPolicies(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
assert.Equal(t, policy.SubnetEgressPriorityDrop, policies[0].Priority)
|
||||
assert.Contains(t, policies[0].Match, topology.SubnetRouterPort("subnet-1"))
|
||||
|
||||
requestOK(t, nc, TopicUngateSubnetEgress,
|
||||
SubnetEgressUngateEvent{VpcId: "vpc-1", SubnetId: "subnet-1", DestinationCidr: "0.0.0.0/0"})
|
||||
|
||||
policies, err = m.ListLogicalRouterPolicies(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, policies, "ungate must remove the DROP policy")
|
||||
}
|
||||
|
||||
func TestHandleSubnetEgress_InvalidCIDR(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
nc, sub, m := runningSubscriber(t)
|
||||
seedVPCWithIGW(t, sub, m, "vpc-1")
|
||||
|
||||
requestFails(t, nc, TopicGateSubnetEgress,
|
||||
SubnetEgressGateEvent{VpcId: "vpc-1", SubnetId: "subnet-1", DestinationCidr: "not-a-cidr"})
|
||||
requestFails(t, nc, TopicUngateSubnetEgress,
|
||||
SubnetEgressUngateEvent{VpcId: "vpc-1", SubnetId: "subnet-1", DestinationCidr: "not-a-cidr"})
|
||||
|
||||
policies, err := m.ListLogicalRouterPolicies(ctx, topology.VPCRouter("vpc-1"))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, policies)
|
||||
}
|
||||
Reference in New Issue
Block a user