Skip to content
Open
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 api/agent/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 29 additions & 12 deletions api/vpc/v1beta1/external_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ var communityCheck = regexp.MustCompile("^(6553[0-5]|655[0-2][0-9]|654[0-9]{2}|6

// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

type ExternalL2 struct {
// Prefixes is the list of IPv4 prefixes reachable via the external
Prefixes []string `json:"prefixes,omitempty"`
}

// ExternalSpec describes IPv4 namespace External belongs to and inbound/outbound communities which are used to
// filter routes from/to the external system.
type ExternalSpec struct {
Expand All @@ -45,6 +50,9 @@ type ExternalSpec struct {
InboundCommunity string `json:"inboundCommunity,omitempty"`
// OutboundCommunity is theoutbound community that all outbound routes will be stamped with (e.g. 50000:50001)
OutboundCommunity string `json:"outboundCommunity,omitempty"`
// L2 contains L2 specific parameters
// +optional
L2 *ExternalL2 `json:"l2,omitempty"`
}

// ExternalStatus defines the observed state of External
Expand Down Expand Up @@ -121,25 +129,34 @@ func (external *External) Validate(ctx context.Context, kube kclient.Reader, _ *
if len(external.Name) > 11 {
return nil, errors.Errorf("name %s is too long, must be <= 11 characters", external.Name)
}

if external.Spec.IPv4Namespace == "" {
return nil, errors.Errorf("IPv4Namespace is required")
}

if external.Spec.InboundCommunity == "" {
return nil, errors.Errorf("inboundCommunity is required")
}
if external.Spec.L2 == nil {
if external.Spec.InboundCommunity == "" {
return nil, errors.Errorf("inboundCommunity is required")
}

if external.Spec.OutboundCommunity == "" {
return nil, errors.Errorf("outboundCommunity is required")
}
if external.Spec.OutboundCommunity == "" {
return nil, errors.Errorf("outboundCommunity is required")
}

if !communityCheck.MatchString(external.Spec.InboundCommunity) {
return nil, errors.Errorf("inboundCommunity %s is not a valid community, example 50000:50001", external.Spec.InboundCommunity)
}
if !communityCheck.MatchString(external.Spec.InboundCommunity) {
return nil, errors.Errorf("inboundCommunity %s is not a valid community, example 50000:50001", external.Spec.InboundCommunity)
}

if !communityCheck.MatchString(external.Spec.OutboundCommunity) {
return nil, errors.Errorf("outboundCommunity %s is not a valid community, example 50000:50001", external.Spec.OutboundCommunity)
}
} else {
if external.Spec.InboundCommunity != "" || external.Spec.OutboundCommunity != "" {
return nil, errors.Errorf("inboundCommunity and outboundCommunity must be empty when L2 is specified")
}

if !communityCheck.MatchString(external.Spec.OutboundCommunity) {
return nil, errors.Errorf("outboundCommunity %s is not a valid community, example 50000:50001", external.Spec.OutboundCommunity)
if len(external.Spec.L2.Prefixes) == 0 {
return nil, errors.Errorf("at least one prefix must be specified in L2 mode")
}
}

if kube != nil {
Expand Down
111 changes: 92 additions & 19 deletions api/vpc/v1beta1/externalattachment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ type ExternalAttachmentSpec struct {
External string `json:"external,omitempty"`
// Connection is the name of the Connection object this attachment belongs to (essentially the name of the switch/port)
Connection string `json:"connection,omitempty"`
// Switch is the switch port configuration for the external attachment
Switch ExternalAttachmentSwitch `json:"switch,omitempty"`
// Neighbor is the BGP neighbor configuration for the external attachment
Neighbor ExternalAttachmentNeighbor `json:"neighbor,omitempty"`
// Switch is the switch port configuration for the external attachment in case of a BGP external
Switch ExternalAttachmentSwitch `json:"switch"`
// Neighbor is the BGP neighbor configuration for the external attachment in case of a BGP external
Neighbor ExternalAttachmentNeighbor `json:"neighbor"`
// L2 contains parameters specific to an L2 external attachment
// +optional
L2 *ExternalAttachmentL2 `json:"l2,omitempty"`
}

// ExternalAttachmentSwitch defines the switch port configuration for the external attachment
Expand All @@ -58,6 +61,18 @@ type ExternalAttachmentNeighbor struct {
IP string `json:"ip,omitempty"`
}

// ExternalAttachmentL2 defines parameters used for L2 external attachments
type ExternalAttachmentL2 struct {
// IP is the actual IP address of the external, which will be used as nexthop for prefixes reachable via this external attachment
IP string `json:"ip"`
// VLAN (optional) is the VLAN ID used for the subinterface on a switch port specified in the connection, set to 0 if no VLAN is required
VLAN uint16 `json:"vlan,omitempty"`
// GatewayIPs is the list of IP addresses (with prefix length) which can be used for NAT on the fabric side for this L2 external attachment
GatewayIPs []string `json:"gatewayIPs"`
// FabricEdgeIP is an IP address (with prefix length) that will be configured on the fabric edge switch; it is needed for proxy-ARP
FabricEdgeIP string `json:"fabricEdgeIP"`
}

// ExternalAttachmentStatus defines the observed state of ExternalAttachment
type ExternalAttachmentStatus struct{}

Expand All @@ -78,7 +93,7 @@ type ExternalAttachment struct {
kmetav1.ObjectMeta `json:"metadata,omitempty"`

// Spec is the desired state of the ExternalAttachment
Spec ExternalAttachmentSpec `json:"spec,omitempty"`
Spec ExternalAttachmentSpec `json:"spec"`
// Status is the observed state of the ExternalAttachment
Status ExternalAttachmentStatus `json:"status,omitempty"`
}
Expand Down Expand Up @@ -134,20 +149,50 @@ func (attach *ExternalAttachment) Validate(ctx context.Context, kube kclient.Rea
if attach.Spec.Connection == "" {
return nil, errors.Errorf("connection is required")
}
if attach.Spec.Switch.IP == "" {
return nil, errors.Errorf("switch.ip is required")
}
if _, _, err := net.ParseCIDR(attach.Spec.Switch.IP); err != nil {
return nil, errors.New("switch.ip is not a valid IP CIDR") //nolint: goerr113
}
if attach.Spec.Neighbor.ASN == 0 {
return nil, errors.Errorf("neighbor.asn is required")
}
if attach.Spec.Neighbor.IP == "" {
return nil, errors.Errorf("neighbor.ip is required")
}
if ip := net.ParseIP(attach.Spec.Neighbor.IP); ip == nil {
return nil, errors.New("neighbor.ip is not a valid IP address") //nolint: goerr113
if attach.Spec.L2 == nil {
if attach.Spec.Switch.IP == "" {
return nil, errors.Errorf("switch.ip is required")
}
if _, _, err := net.ParseCIDR(attach.Spec.Switch.IP); err != nil {
return nil, errors.New("switch.ip is not a valid IP CIDR") //nolint: goerr113
}
if attach.Spec.Neighbor.ASN == 0 {
return nil, errors.Errorf("neighbor.asn is required")
}
if attach.Spec.Neighbor.IP == "" {
return nil, errors.Errorf("neighbor.ip is required")
}
if ip := net.ParseIP(attach.Spec.Neighbor.IP); ip == nil {
return nil, errors.New("neighbor.ip is not a valid IP address") //nolint: goerr113
}
} else {
if attach.Spec.Switch.IP != "" || attach.Spec.Switch.VLAN != 0 {
return nil, errors.Errorf("switch parameters must not be set for L2 external attachment")
}
if attach.Spec.Neighbor.ASN != 0 || attach.Spec.Neighbor.IP != "" {
return nil, errors.Errorf("neighbor parameters must not be set for L2 external attachment")
}
if attach.Spec.L2.IP == "" {
return nil, errors.Errorf("l2.ip is required for L2 external attachment")
}
if ip := net.ParseIP(attach.Spec.L2.IP); ip == nil {
return nil, errors.New("l2.ip is not a valid IP address") //nolint: goerr113
}
if len(attach.Spec.L2.GatewayIPs) == 0 {
return nil, errors.Errorf("at least one l2.gatewayIPs is required for L2 external attachment")
}
for _, cidr := range attach.Spec.L2.GatewayIPs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
return nil, errors.Errorf("l2.gatewayIPs contains an invalid address (with prefix length): %s", cidr) //nolint: goerr113
}
}
// TODO: make this optional and auto-assign from a pool?
if attach.Spec.L2.FabricEdgeIP == "" {
return nil, errors.Errorf("l2.fabricEdgeIP is required for L2 external attachment")
}
if _, _, err := net.ParseCIDR(attach.Spec.L2.FabricEdgeIP); err != nil {
return nil, errors.Wrapf(err, "l2.fabricEdgeIP is not a valid IP address with prefix length")
}
}

if kube != nil {
Expand All @@ -159,6 +204,12 @@ func (attach *ExternalAttachment) Validate(ctx context.Context, kube kclient.Rea

return nil, errors.Wrapf(err, "failed to read external %s", attach.Spec.External) // TODO replace with some internal error to not expose to the user
}
if attach.Spec.L2 != nil && ext.Spec.L2 == nil {
return nil, errors.Errorf("external attachment is L2 but external %s is not", attach.Spec.External)
}
if attach.Spec.L2 == nil && ext.Spec.L2 != nil {
return nil, errors.Errorf("external attachment is not L2 but external %s is", attach.Spec.External)
}

conn := &wiringapi.Connection{}
if err := kube.Get(ctx, ktypes.NamespacedName{Name: attach.Spec.Connection, Namespace: attach.Namespace}, conn); err != nil {
Expand All @@ -172,6 +223,28 @@ func (attach *ExternalAttachment) Validate(ctx context.Context, kube kclient.Rea
if conn.Spec.External == nil {
return nil, errors.Errorf("connection %s is not external", attach.Spec.Connection)
}

// validate VLAN collision
attaches := &ExternalAttachmentList{}
if err := kube.List(ctx, attaches, kclient.MatchingLabels{wiringapi.LabelName("connection"): attach.Spec.Connection}); err != nil {
return nil, errors.Wrapf(err, "failed to list external attachments for %s", attach.Spec.Connection) // TODO replace with some internal error to not expose to the user
}
ourVLAN := attach.Spec.Switch.VLAN
if attach.Spec.L2 != nil {
ourVLAN = attach.Spec.L2.VLAN
}
for _, other := range attaches.Items {
if other.Name == attach.Name {
continue
}
otherVLAN := other.Spec.Switch.VLAN
if other.Spec.L2 != nil {
otherVLAN = other.Spec.L2.VLAN
}
if otherVLAN == ourVLAN {
return nil, errors.Errorf("connection %s already has an external attachment with VLAN %d", attach.Spec.Connection, ourVLAN)
}
}
}

return nil, nil
Expand Down
Loading
Loading