Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move common functions in netbox/utils sub-module #9

Merged
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
157 changes: 13 additions & 144 deletions pkg/connector/netbox/netbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ package netbox

import (
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

dbModel "github.com/cloudflare/octopus/pkg/connector/netbox/model"
nbUtils "github.com/cloudflare/octopus/pkg/connector/netbox/utils"
"github.com/cloudflare/octopus/pkg/model"
"github.com/cloudflare/octopus/proto/octopus"

Expand All @@ -29,10 +28,6 @@ const (
updateInterval = time.Minute * 2
)

var (
ifNameTagsRegexp = regexp.MustCompile(`^(?P<intf_name>.+?)\.((?P<outer_tag>\d+)\.)?(?P<inner_tag>\d+)$`)
)

type NetboxConnector struct {
connectorMu sync.RWMutex
client NetboxClientI
Expand Down Expand Up @@ -192,7 +187,7 @@ func (n *NetboxConnector) addDevices(t *model.Topology) error {
topoDev.Role = d.DeviceRole.Slug
topoDev.DeviceType = d.DeviceType.Slug

md, err := metaDataFromTags(d.Tags)
md, err := nbUtils.GetMetaDataFromTags(d.Tags)
if err != nil {
return fmt.Errorf("unable to get meta data: %v", err)
}
Expand All @@ -218,7 +213,7 @@ func (n *NetboxConnector) addInterfaces(t *model.Topology) error {
t.DevicesByInterfaceID[nbIfa.ID] = d
t.Interfaces[nbIfa.ID] = ifa

md, err := metaDataFromTags(nbIfa.Tags)
md, err := nbUtils.GetMetaDataFromTags(nbIfa.Tags)
if err != nil {
return fmt.Errorf("unable to get meta data: %v", err)
}
Expand Down Expand Up @@ -255,15 +250,15 @@ func (n *NetboxConnector) addInterfaceUnits(t *model.Topology) error {
return fmt.Errorf("can not find interface %s:%s", nbIfa.Device.Name, nbIfa.Parent.Name)
}

vlanTag, err := parseUnitStr(unitStr)
vlanTag, err := nbUtils.ParseUnitStr(unitStr)
if err != nil {
return fmt.Errorf("Unable to convert unit %q (id=%d) (interface %q) to int for %s:%s. Ignoring logical interface.", unitStr, nbIfa.ID, nbIfa.Name, nbIfa.Device.Name, nbIfa.Parent.Name)
return fmt.Errorf("unable to convert unit %q (id=%d) (interface %q) to int for %s:%s. Ignoring logical interface", unitStr, nbIfa.ID, nbIfa.Name, nbIfa.Device.Name, nbIfa.Parent.Name)
}

u := ifa.AddUnitIfNotExists(vlanTag)
t.DevicesByInterfaceID[nbIfa.ID] = d

md, err := metaDataFromTags(nbIfa.Tags)
md, err := nbUtils.GetMetaDataFromTags(nbIfa.Tags)
if err != nil {
return fmt.Errorf("unable to get meta data: %v", err)
}
Expand All @@ -274,34 +269,6 @@ func (n *NetboxConnector) addInterfaceUnits(t *model.Topology) error {
return nil
}

func parseUnitStr(unitStr string) (model.VLANTag, error) {
if strings.Contains(unitStr, ".") {
parts := strings.Split(unitStr, ".")
if len(parts) != 2 {
return model.VLANTag{}, fmt.Errorf("invalid unit string %q", unitStr)
}

outerTag, err := strconv.Atoi(parts[0])
if err != nil {
return model.VLANTag{}, fmt.Errorf("unable to convert %q to int: %v", parts[0], err)
}

innerTag, err := strconv.Atoi(parts[1])
if err != nil {
return model.VLANTag{}, fmt.Errorf("unable to convert %q to int: %v", parts[1], err)
}

return model.NewVLANTag(uint16(outerTag), uint16(innerTag)), nil
}

ctag, err := strconv.Atoi(unitStr)
if err != nil {
return model.VLANTag{}, fmt.Errorf("unable to convert %q to int: %v", unitStr, err)
}

return model.NewVLANTag(0, uint16(ctag)), nil
}

func (n *NetboxConnector) addIPAddresses(t *model.Topology) error {
for _, nbIP := range n.ipAddresses {
if nbIP.AssignedObjectID == 0 || nbIP.AssignedObjectTypeID != n.client.GetDcimInterfaceTypeID() {
Expand All @@ -323,18 +290,18 @@ func (n *NetboxConnector) addIPAddresses(t *model.Topology) error {
return fmt.Errorf("interface with id %d not found", ifaID)
}

_, vt, err := getInterfaceAndVLANTag(dcimIfa.Name)
_, vt, err := nbUtils.GetInterfaceAndVLANTag(dcimIfa.Name)
if err != nil {
return fmt.Errorf("unable to extract interface name and unit from %q: %v", dcimIfa.Name, err)
}

pfx, err := bnet.PrefixFromString(sanitizeIPAddress(nbIP.Address))
pfx, err := bnet.PrefixFromString(nbUtils.SanitizeIPAddress(nbIP.Address))
if err != nil {
return fmt.Errorf("failed to parse IP %q: %v", nbIP.Address, err)
}

ip := model.NewIP(*pfx)
getCustomFieldData(ip.MetaData, nbIP.CustomFieldData)
nbUtils.GetCustomFieldData(ip.MetaData, nbIP.CustomFieldData)

u := ifa.AddUnitIfNotExists(vt)
if pfx.Addr().IsIPv4() {
Expand All @@ -347,26 +314,14 @@ func (n *NetboxConnector) addIPAddresses(t *model.Topology) error {
return nil
}

func sanitizeIPAddress(addr string) string {
if strings.Contains(addr, "/") {
return addr
}

if strings.Contains(addr, ".") {
return addr + "/32"
}

return addr + "/128"
}

func (n *NetboxConnector) addPrefixes(t *model.Topology) error {
for _, p := range n.prefixes {
pfx, err := bnet.PrefixFromString(p.Prefix)
if err != nil {
return fmt.Errorf("failed to parse Prefix %q: %v", p.Prefix, err)
}

md, err := metaDataFromTags(p.Tags)
md, err := nbUtils.GetMetaDataFromTags(p.Tags)
if err != nil {
return fmt.Errorf("failed to get Tags for Prefix %q: %v", p.Prefix, err)
}
Expand All @@ -380,36 +335,6 @@ func (n *NetboxConnector) addPrefixes(t *model.Topology) error {
return nil
}

func metaDataFromTags(tags []string) (*model.MetaData, error) {
ret := model.NewMetaData()
for _, tag := range tags {
parts := strings.Split(tag, "=")

// Semantic Tag
if len(parts) == 2 {
if _, exists := ret.SemanticTags[parts[0]]; exists {
return nil, fmt.Errorf("Key %q exists already: %q vs. %q", parts[0], ret.SemanticTags[parts[0]], parts[1])
}

ret.SemanticTags[parts[0]] = parts[1]

} else {
// Regular Tag
ret.Tags = append(ret.Tags, tag)
}
}

return ret, nil
}

func getCustomFieldData(md *model.MetaData, customFieldData string) {
if customFieldData == "" || customFieldData == "{}" {
return
}

md.CustomFieldData = customFieldData
}

func (n *NetboxConnector) getCableEnd(terminationType int32, terminationID int64, t *model.Topology) (*model.CableEnd, error) {
ce := model.CableEnd{}

Expand Down Expand Up @@ -483,7 +408,7 @@ func (n *NetboxConnector) getCableEnd(terminationType int32, terminationID int64

default:
// consoleport, consoleserverport, powerport, or poweroutlet
return nil, fmt.Errorf("Don't know what to do with cable termination ID %d (type %d)", terminationID, terminationType)
return nil, fmt.Errorf("don't know what to do with cable termination ID %d (type %d)", terminationID, terminationType)
}

return &ce, nil
Expand Down Expand Up @@ -528,7 +453,7 @@ func (n *NetboxConnector) addRearPorts(t *model.Topology) error {
for _, rp := range n.rearPorts {
nbDev := n.devices[rp.DeviceID]
if nbDev == nil {
return fmt.Errorf("Device %d not found", rp.DeviceID)
return fmt.Errorf("device %d not found", rp.DeviceID)
}

d := t.GetDevice(nbDev.Name)
Expand All @@ -549,7 +474,7 @@ func (n *NetboxConnector) addFrontPorts(t *model.Topology) error {
for _, fp := range n.frontPorts {
nbDev := n.devices[fp.DeviceID]
if nbDev == nil {
return fmt.Errorf("Device %d not found", fp.DeviceID)
return fmt.Errorf("device %d not found", fp.DeviceID)
}

d := t.GetDevice(nbDev.Name)
Expand All @@ -573,62 +498,6 @@ func (n *NetboxConnector) addFrontPorts(t *model.Topology) error {
return nil
}

func getInterfaceAndVLANTag(name string) (ifName string, vt model.VLANTag, err error) {
if isLogicalInterface(name) {
return extractInterfaceAndUnit(name)
}

return name, model.VLANTag{}, nil
}

func isLogicalInterface(name string) bool {
return strings.Contains(name, ".")
}

func extractInterfaceAndUnit(name string) (string, model.VLANTag, error) {
extractedVars := reSubMatchMap(ifNameTagsRegexp, name)
if _, exists := extractedVars["intf_name"]; !exists {
return "", model.VLANTag{}, fmt.Errorf("unable to extract interface name from %q", name)
}

if _, exists := extractedVars["inner_tag"]; !exists {
return "", model.VLANTag{}, fmt.Errorf("unable to extract inner tag from %q", name)
}

innerTag, err := strconv.Atoi(extractedVars["inner_tag"])
if err != nil {
return "", model.VLANTag{}, fmt.Errorf("unable to convert inner tag from string %q to int (%q)", extractedVars["inner_tag"], name)
}

vt := model.VLANTag{
InnerTag: uint16(innerTag),
}

outerTagStr := extractedVars["outer_tag"]
if outerTagStr != "" {
outerTag, err := strconv.Atoi(outerTagStr)
if err != nil {
return "", model.VLANTag{}, fmt.Errorf("unable to convert outer tag from string %q to int (%q)", outerTagStr, name)
}

vt.OuterTag = uint16(outerTag)
}

return extractedVars["intf_name"], vt, nil
}

func reSubMatchMap(r *regexp.Regexp, str string) map[string]string {
match := r.FindStringSubmatch(str)
subMatchMap := make(map[string]string)
for i, name := range r.SubexpNames() {
if i != 0 && name != "" && i <= len(match) {
subMatchMap[name] = match[i]
}
}

return subMatchMap
}

func (n *NetboxConnector) StartRefreshRoutine() {
go n.refreshRoutine()
}
Expand Down
52 changes: 0 additions & 52 deletions pkg/connector/netbox/netbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1019,55 +1019,3 @@ func TestEnrichment(t *testing.T) {
assert.Equal(t, test.expected, test.t.ToProto(), test.name)
}
}

func TestExtractInterfaceAndUnit(t *testing.T) {
tests := []struct {
name string
input string
expectedName string
expectedVLANTag model.VLANTag
wantFail bool
}{
{
name: "dot1q",
input: "vlan.900",
expectedName: "vlan",
expectedVLANTag: model.NewVLANTag(0, 900),
},
{
name: "Q-in-Q",
input: "xe-0/0/0.100.200",
expectedName: "xe-0/0/0",
expectedVLANTag: model.NewVLANTag(100, 200),
},
{
name: "broken",
input: "xe-0/0/0",
expectedName: "xe-0/0/0",
expectedVLANTag: model.NewVLANTag(100, 200),
wantFail: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ifName, vt, err := extractInterfaceAndUnit(test.input)
if err != nil {
if test.wantFail {
return
}

t.Errorf("unexpected failure of test %q: %v", test.name, err)
return
}

if test.wantFail {
t.Errorf("unexpected success of test %q", test.name)
return
}

assert.Equal(t, test.expectedName, ifName, test.name)
assert.Equal(t, test.expectedVLANTag, vt, test.name)
})
}
}
Loading
Loading