Skip to content

Commit

Permalink
[vlab] Generate virtual external config (#51)
Browse files Browse the repository at this point in the history
Virtual External config is generated based on last orphan leaf in the
wiring diagram, so at least one orphan leaf is mandatory.
  • Loading branch information
sergeymatov authored Mar 13, 2024
1 parent f291afa commit 5ce78bb
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 12 deletions.
8 changes: 8 additions & 0 deletions cmd/hhfab/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func main() {
var wgChainControlLink bool
var wgControlLinksCount, wgSpinesCount, wgFabricLinksCount, wgMCLAGLeafsCount, wgOrphanLeafsCount, wgMCLAGSessionLinks, wgMCLAGPeerLinks, wgVPCLoopbacks uint
var wgESLAGLeafGroups string
var wgExternal bool

fabricModes := []string{}
for _, m := range meta.FabricModes {
Expand Down Expand Up @@ -189,6 +190,12 @@ func main() {
Usage: "number of vpc loopbacks for each switch",
Destination: &wgVPCLoopbacks,
},
&cli.BoolFlag{
Category: FLAG_CATEGORY_WIRING_GEN,
Name: "external",
Usage: "include vritual external switch",
Destination: &wgExternal,
},
}

mngr := fab.NewCNCManager()
Expand Down Expand Up @@ -251,6 +258,7 @@ func main() {
wiringGen := &wiring.Builder{
FabricMode: meta.FabricMode(fabricMode),
ChainControlLink: wgChainControlLink,
External: wgExternal,
ControlLinksCount: uint8(wgControlLinksCount),
SpinesCount: uint8(wgSpinesCount),
FabricLinksCount: uint8(wgFabricLinksCount),
Expand Down
46 changes: 44 additions & 2 deletions pkg/fab/wiring/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ import (
)

const (
RACK = "rack-1"
CONTROL = "control-1"
RACK = "rack-1"
CONTROL = "control-1"
EXTERNAL = "virtual-edge-1"
VIRTUAL_EDGE_DEST = "external.hhfab.fabric.githedgehog.com/dest" // HHFAB annotation to specify destination for external connection
)

type Builder struct {
Defaulted bool // true if default should be called on created objects
Hydrated bool // true if wiring diagram should be hydrated
FabricMode meta.FabricMode // fabric mode
ChainControlLink bool // true if not all switches attached directly to control node
External bool // true if virtual external should be applied
ControlLinksCount uint8 // number of control links to generate
SpinesCount uint8 // number of spines to generate
FabricLinksCount uint8 // number of links for each spine <> leaf pair
Expand Down Expand Up @@ -72,6 +75,9 @@ func (b *Builder) Build() (*wiring.Data, error) {
if b.ChainControlLink {
return nil, fmt.Errorf("control link chaining not supported for collapsed core fabric mode")
}
if b.External {
return nil, fmt.Errorf("external not supported for collapsed core fabric mode")
}
if b.SpinesCount > 0 {
return nil, fmt.Errorf("spines not supported for collapsed core fabric mode")
}
Expand Down Expand Up @@ -572,6 +578,27 @@ func (b *Builder) Build() (*wiring.Data, error) {
}
}

// external switch. For now it's only orphan leafs
if b.External {
if b.OrphanLeafsCount < 1 {
return nil, fmt.Errorf("external switch requires at least one orphan leaf")
}
if _, err := b.createSwitch(EXTERNAL, wiringapi.SwitchSpec{
Role: wiringapi.SwitchRoleVirtualEdge,
Description: "Virtual edge",
}); err != nil {
return nil, err
}
if _, err := b.createControlConnection(EXTERNAL); err != nil {
return nil, err
}
// (LeafID - 1) is now pointing on the last Orphan Leaf. We will use it to connect external switch to the last orphan leaf
if _, err := b.createExternalConnection(fmt.Sprintf("leaf-%02d", leafID-1)); err != nil {
return nil, err
}
leafID++
}

for spineID := uint8(1); spineID <= b.SpinesCount; spineID++ {
spineName := fmt.Sprintf("spine-%02d", spineID)

Expand Down Expand Up @@ -813,3 +840,18 @@ func (b *Builder) createControlConnection(switchName string) (*wiringapi.Connect
},
})
}

func (b *Builder) createExternalConnection(switchName string) (*wiringapi.Connection, error) {
conn, err := b.createConnection(wiringapi.ConnectionSpec{
External: &wiringapi.ConnExternal{
Link: wiringapi.ConnExternalLink{
Switch: wiringapi.BasePortName{Port: b.nextSwitchPort(switchName)},
},
},
})

conn.Annotations = make(map[string]string)
conn.Annotations[VIRTUAL_EDGE_DEST] = fmt.Sprintf("%s", b.nextSwitchPort(string(EXTERNAL))) // TODO: Generate it from CLI

return conn, err
}
134 changes: 124 additions & 10 deletions pkg/fab/wiring/hydrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
package wiring

import (
"encoding/json"
"fmt"
"os"
"slices"
"strconv"
"strings"

"github.com/pkg/errors"
agentapi "go.githedgehog.com/fabric/api/agent/v1alpha2"
vpcapi "go.githedgehog.com/fabric/api/vpc/v1alpha2"
wiringapi "go.githedgehog.com/fabric/api/wiring/v1alpha2"
"go.githedgehog.com/fabric/pkg/wiring"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func IsHydrated(data *wiring.Data) error {
Expand Down Expand Up @@ -75,6 +80,58 @@ type HydrateConfig struct {
LeafASNStart uint32
}

func createExternal(e agentapi.VirtualEdgeConfig, data *wiring.Data) error {
external := &vpcapi.External{
TypeMeta: metav1.TypeMeta{
Kind: "External",
APIVersion: vpcapi.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "virtual-edge",
},
Spec: vpcapi.ExternalSpec{
IPv4Namespace: "default",
InboundCommunity: e.CommunityOut,
OutboundCommunity: e.CommunityIn,
},
}
return errors.Wrapf(data.Add(external), "error adding external object")
}

func createExternalAttachment(e agentapi.VirtualEdgeConfig, data *wiring.Data, conn string) error {
vlan, err := strconv.ParseUint(e.IfVlan, 10, 16)
if err != nil {
return errors.Wrapf(err, "error parsing VLAN %s", e.IfVlan)
}

virtualEdgeIPBits := strings.Split(e.IfIP, "/")
virtualEdgeIP := virtualEdgeIPBits[0]

attachment := &vpcapi.ExternalAttachment{
TypeMeta: metav1.TypeMeta{
Kind: "ExternalAttachment",
APIVersion: vpcapi.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "virtual-edge-attachment",
},
Spec: vpcapi.ExternalAttachmentSpec{
External: "virtual-edge",
Connection: conn,
Switch: vpcapi.ExternalAttachmentSwitch{
VLAN: uint16(vlan),
IP: fmt.Sprintf("%s/24", e.NeighborIP),
},
Neighbor: vpcapi.ExternalAttachmentNeighbor{
ASN: VIRTUAL_EDGE_ASN,
IP: virtualEdgeIP,
},
},
}

return errors.Wrapf(data.Add(attachment), "error adding external attachment object")
}

const (
SPINE_OFFSET = 200
LEAF_OFFSET = 100
Expand All @@ -86,6 +143,9 @@ const (
VTEP_IP_NET = 12
CONTROL_IP_NET = 20 // single /24 is more than enough
FABRIC_IP_NET = 30 // can take more than one /24, let's book 10
VIRTUAL_EDGE_IP_NET = 40 // single /24 is more than enough
VIRTUAL_EDGE_CFG = "virtual-edge.hhfab.fabric.githedgehog.com/external-cfg"
VIRTUAL_EDGE_ASN = 64100
)

func HydratePath(wiringPath string) error {
Expand Down Expand Up @@ -129,21 +189,35 @@ func Hydrate(data *wiring.Data, cfg *HydrateConfig) error {
}

mclagPeer := map[string]string{}
var externalSwitches []string
var externalConnections []string
for _, conn := range data.Connection.All() {
if conn.Spec.MCLAGDomain == nil {
continue
}
if conn.Spec.MCLAGDomain != nil {

sws, _, _, _, err := conn.Spec.Endpoints()
if err != nil {
return errors.Wrapf(err, "error getting endpoints for MCLAG domain connection %s", conn.Name)
sws, _, _, _, err := conn.Spec.Endpoints()
if err != nil {
return errors.Wrapf(err, "error getting endpoints for MCLAG domain connection %s", conn.Name)
}
if len(sws) != 2 {
return errors.Errorf("MCLAG domain connection %s has %d endpoints, expected 2", conn.Name, len(sws))
}

mclagPeer[sws[0]] = sws[1]
mclagPeer[sws[1]] = sws[0]
}
if len(sws) != 2 {
return errors.Errorf("MCLAG domain connection %s has %d endpoints, expected 2", conn.Name, len(sws))
if conn.Spec.External != nil {

sws, _, _, _, err := conn.Spec.Endpoints()
if err != nil {
return errors.Wrapf(err, "error getting endpoints for external connection %s", conn.Name)
}
if len(sws) != 1 {
return errors.Errorf("external connection %s has %d endpoints, expected 1", conn.Name, len(sws))
}
externalSwitches = append(externalSwitches, sws[0])
externalConnections = append(externalConnections, conn.Name)
}

mclagPeer[sws[0]] = sws[1]
mclagPeer[sws[1]] = sws[0]
}

spine := 0
Expand Down Expand Up @@ -174,6 +248,46 @@ func Hydrate(data *wiring.Data, cfg *HydrateConfig) error {

leaf++
}
if sw.Spec.Role.IsVirtualEdge() {
sw.Spec.ASN = VIRTUAL_EDGE_ASN
sw.Spec.IP = fmt.Sprintf("%s.%d.%d/32", cfg.Subnet, SWITCH_IP_NET, leaf+LEAF_OFFSET)
sw.Spec.ProtocolIP = fmt.Sprintf("%s.%d.%d/32", cfg.Subnet, PROTOCOL_IP_NET, leaf+LEAF_OFFSET)
if len(externalSwitches) != 1 {
return errors.Errorf("expected exactly one external switch for virtual edge, got %d", len(externalSwitches))
}

if borderSw := data.Switch.Get(externalSwitches[0]); borderSw != nil {
externalConfig := agentapi.VirtualEdgeConfig{
ASN: fmt.Sprintf("%d", borderSw.Spec.ASN),
VRF: "default",
CommunityIn: fmt.Sprintf("%d:%d", VIRTUAL_EDGE_ASN, borderSw.Spec.ASN),
CommunityOut: fmt.Sprintf("%d:%d", borderSw.Spec.ASN, VIRTUAL_EDGE_ASN),
NeighborIP: fmt.Sprintf("%s.%d.%d", cfg.Subnet, VIRTUAL_EDGE_IP_NET, 1),
IfName: "Ethernet1",
IfVlan: "200",
IfIP: fmt.Sprintf("%s.%d.%d/24", cfg.Subnet, VIRTUAL_EDGE_IP_NET, leaf+LEAF_OFFSET),
}

encodedConfig := map[string]agentapi.VirtualEdgeConfig{}
encodedConfig[borderSw.Name] = externalConfig
encoded, err := json.Marshal(encodedConfig)
if err != nil {
return errors.Wrapf(err, "error encoding external config")
}
sw.Annotations = make(map[string]string)
sw.Annotations[VIRTUAL_EDGE_CFG] = string(encoded)

err = createExternal(externalConfig, data)
if err != nil {
return errors.Wrapf(err, "error creating external object")
}

err = createExternalAttachment(externalConfig, data, externalConnections[0])
if err != nil {
return errors.Wrapf(err, "error creating external attachment object")
}
}
}

if err := data.Update(sw); err != nil {
return errors.Wrapf(err, "error updating switch %s", sw.Name)
Expand Down

0 comments on commit 5ce78bb

Please sign in to comment.