Skip to content

Commit

Permalink
Merge pull request #27 from jdelnano/jdelnano/bgp-support
Browse files Browse the repository at this point in the history
Add Marshalling and BGP Instance resource support
  • Loading branch information
ddelnano authored Jan 27, 2021
2 parents 4ee3f3f + 385538f commit b120646
Show file tree
Hide file tree
Showing 9 changed files with 866 additions and 14 deletions.
122 changes: 122 additions & 0 deletions client/bgp_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package client

import (
"fmt"
"log"
"strings"
)

// BgpInstance Mikrotik resource
type BgpInstance struct {
ID string `mikrotik:".id"`
Name string `mikrotik:"name"`
As int `mikrotik:"as"`
ClientToClientReflection bool `mikrotik:"client-to-client-reflection"`
Comment string `mikrotik:"comment"`
ConfederationPeers string `mikrotik:"confederation-peers"`
Disabled bool `mikrotik:"disabled"`
IgnoreAsPathLen bool `mikrotik:"ignore-as-path-len"`
OutFilter string `mikrotik:"out-filter"`
RedistributeConnected bool `mikrotik:"redistribute-connected"`
RedistributeOspf bool `mikrotik:"redistribute-ospf"`
RedistributeOtherBgp bool `mikrotik:"redistribute-other-bgp"`
RedistributeRip bool `mikrotik:"redistribute-rip"`
RedistributeStatic bool `mikrotik:"redistribute-static"`
RouterID string `mikrotik:"router-id"`
RoutingTable string `mikrotik:"routing-table"`
ClusterID string `mikrotik:"cluster-id"`
Confederation int `mikrotik:"confederation"`
}

// AddBgpInstance Mikrotik resource
func (client Mikrotik) AddBgpInstance(b *BgpInstance) (*BgpInstance, error) {
c, err := client.getMikrotikClient()

if err != nil {
return nil, err
}

attributes := Marshal(b)
cmd := strings.Split(fmt.Sprintf("/routing/bgp/instance/add %s", attributes), " ")

log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
r, err := c.RunArgs(cmd)
log.Printf("[DEBUG] /routing/bgp/instance/add returned %v", r)

if err != nil {
return nil, err
}

return client.FindBgpInstance(b.Name)
}

// FindBgpInstance Mikrotik resource
func (client Mikrotik) FindBgpInstance(name string) (*BgpInstance, error) {
c, err := client.getMikrotikClient()
if err != nil {
return nil, err
}

cmd := strings.Split(fmt.Sprintf("/routing/bgp/instance/print ?name=%s", name), " ")
log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
r, err := c.RunArgs(cmd)

if err != nil {
return nil, err
}

log.Printf("[DEBUG] Find bgp instance: `%v`", cmd)

bgpInstance := BgpInstance{}

err = Unmarshal(*r, &bgpInstance)

if err != nil {
return nil, err
}

if bgpInstance.Name == "" {
return nil, NewNotFound(fmt.Sprintf("bgp instance `%s` not found", name))
}

return &bgpInstance, nil
}

// UpdateBgpInstance Mikrotik resource
func (client Mikrotik) UpdateBgpInstance(b *BgpInstance) (*BgpInstance, error) {
c, err := client.getMikrotikClient()

if err != nil {
return nil, err
}

attributes := Marshal(b)
cmd := strings.Split(fmt.Sprintf("/routing/bgp/instance/set %s", attributes), " ")

log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
_, err = c.RunArgs(cmd)

if err != nil {
return nil, err
}

return client.FindBgpInstance(b.Name)
}

// DeleteBgpInstance Mikrotik resource
func (client Mikrotik) DeleteBgpInstance(name string) error {
c, err := client.getMikrotikClient()

bgpInstance, err := client.FindBgpInstance(name)

if err != nil {
return err
}

cmd := strings.Split(fmt.Sprintf("/routing/bgp/instance/remove =numbers=%s", bgpInstance.Name), " ")
log.Printf("[INFO] Running the mikrotik command: `%s`", cmd)
r, err := c.RunArgs(cmd)
log.Printf("[DEBUG] Remove bgp instance via mikrotik api: %v", r)

return err
}
123 changes: 123 additions & 0 deletions client/bgp_instance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package client

import (
"reflect"
"testing"
)

var bgpName string = "test-bgp"
var as int = 65533
var updatedAs int = 65534
var clientToClientReflection bool = true
var clusterID string = "172.21.16.1"
var noClusterID string = ""
var bgpComment string = "test-comment"
var confederation int = 8
var updatedConfederation int = 5
var confederationPeers string = ""
var disabled bool = false
var ignoreAsPathLen bool = false
var outFilter string = ""
var redistributeConnected bool = false
var redistributeOspf bool = false
var redistributeOtherBgp bool = false
var redistributeRip bool = false
var redistributeStatic bool = false
var routerID string = "172.21.16.2"
var routingTable string = ""

func TestAddBgpInstanceAndDeleteBgpInstance(t *testing.T) {
c := NewClient(GetConfigFromEnv())

expectedBgpInstance := &BgpInstance{
Name: bgpName,
As: as,
ClientToClientReflection: clientToClientReflection,
IgnoreAsPathLen: ignoreAsPathLen,
OutFilter: outFilter,
RedistributeConnected: redistributeConnected,
RedistributeOspf: redistributeOspf,
RedistributeOtherBgp: redistributeOtherBgp,
RedistributeRip: redistributeRip,
RedistributeStatic: redistributeStatic,
RouterID: routerID,
RoutingTable: routingTable,
}
bgpInstance, err := c.AddBgpInstance(expectedBgpInstance)
if err != nil {
t.Fatalf("Error creating a bpg instance with: %v", err)
}

expectedBgpInstance.ID = bgpInstance.ID

if !reflect.DeepEqual(bgpInstance, expectedBgpInstance) {
t.Errorf("The bgp instance does not match what we expected. actual: %v expected: %v", bgpInstance, expectedBgpInstance)
}

err = c.DeleteBgpInstance(bgpInstance.Name)

if err != nil {
t.Errorf("Error deleting bgp instance with: %v", err)
}
}

func TestAddAndUpdateBgpInstanceWithOptionalFieldsAndDeleteBgpInstance(t *testing.T) {
c := NewClient(GetConfigFromEnv())

expectedBgpInstance := &BgpInstance{
Name: bgpName,
As: as,
ClientToClientReflection: clientToClientReflection,
Comment: bgpComment,
ConfederationPeers: confederationPeers,
Disabled: disabled,
IgnoreAsPathLen: ignoreAsPathLen,
OutFilter: outFilter,
RedistributeConnected: redistributeConnected,
RedistributeOspf: redistributeOspf,
RedistributeOtherBgp: redistributeOtherBgp,
RedistributeRip: redistributeRip,
RedistributeStatic: redistributeStatic,
RouterID: routerID,
RoutingTable: routingTable,
ClusterID: clusterID,
Confederation: confederation,
}
bgpInstance, err := c.AddBgpInstance(expectedBgpInstance)
if err != nil {
t.Fatalf("Error creating a bpg instance with: %v", err)
}

expectedBgpInstance.ID = bgpInstance.ID

if !reflect.DeepEqual(bgpInstance, expectedBgpInstance) {
t.Errorf("The bgp instance does not match what we expected. actual: %v expected: %v", bgpInstance, expectedBgpInstance)
}

// update fields
expectedBgpInstance.Confederation = updatedConfederation
expectedBgpInstance.As = updatedAs

bgpInstance, err = c.UpdateBgpInstance(expectedBgpInstance)

if !reflect.DeepEqual(bgpInstance, expectedBgpInstance) {
t.Errorf("The bgp instance does not match what we expected. actual: %v expected: %v", bgpInstance, expectedBgpInstance)
}

err = c.DeleteBgpInstance(bgpInstance.Name)

if err != nil {
t.Errorf("Error deleting bgp instance with: %v", err)
}
}

func TestFindBgpInstance_onNonExistantBgpInstance(t *testing.T) {
c := NewClient(GetConfigFromEnv())

name := "bgp instance does not exist"
_, err := c.FindBgpInstance(name)

if _, ok := err.(*NotFound); !ok {
t.Errorf("Expecting to receive NotFound error for bgp instance `%s`, instead error was nil.", name)
}
}
49 changes: 49 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ func parseStruct(v *reflect.Value, sentence proto.Sentence) {
case reflect.Int:
if contains(tags, "ttlToSeconds") {
field.SetInt(int64(ttlToSeconds(pair.Value)))
} else {
intValue, _ := strconv.Atoi(pair.Value)
field.SetInt(int64(intValue))
}
}

Expand Down Expand Up @@ -163,3 +166,49 @@ func (client Mikrotik) getMikrotikClient() (c *routeros.Client, err error) {

return
}

func boolToMikrotikBool(b bool) string {
if b {
return "yes"
} else {
return "no"
}
}

func Marshal(s interface{}) string {
var elem reflect.Value
rv := reflect.ValueOf(s)

if rv.Kind() == reflect.Ptr {
// get Value of what pointer points to
elem = rv.Elem()
} else {
elem = rv
}

var attributes []string

for i := 0; i < elem.NumField(); i++ {
value := elem.Field(i)
fieldType := elem.Type().Field(i)
// supports multiple struct tags--assumes first is mikrotik field name
tag := strings.Split(fieldType.Tag.Get("mikrotik"), ",")[0]

if tag != "" && (!value.IsZero() || value.Kind() == reflect.Bool) {
switch value.Kind() {
case reflect.Int:
intValue := elem.Field(i).Interface().(int)
attributes = append(attributes, fmt.Sprintf("=%s=%d", tag, intValue))
case reflect.String:
stringValue := elem.Field(i).Interface().(string)
attributes = append(attributes, fmt.Sprintf("=%s=%s", tag, stringValue))
case reflect.Bool:
boolValue := elem.Field(i).Interface().(bool)
stringBoolValue := boolToMikrotikBool(boolValue)
attributes = append(attributes, fmt.Sprintf("=%s=%s", tag, stringBoolValue))
}
}
}

return strings.Join(attributes, " ")
}
Loading

0 comments on commit b120646

Please sign in to comment.