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

Added support for FSOS provider #3

Merged
merged 8 commits into from
Sep 6, 2023
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testdata/fsos/*.txt text eol=crlf
64 changes: 51 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,66 @@
# Switch Manager

This is a simple switch manager library, written in Go. Currently, it supports only
Juniper, Juniper ELS and FSCom switches, but can be extended easily. Extensive
tests ensure that the library works as expected, but this is no guarantee. Software
updates for the switches can change the behavior of the switches and therefore break
the library. Always keep this in mind.
This is a simple switch management library, written in Go. Currently, it supports only
Juniper, Juniper ELS and FScom switches, but can be extended easily.

Extensive tests ensure that the library works as expected, but this is no guarantee. Software
updates for the switches can change the behavior of the switches and therefore break the library.

# Supported Vendors

- Juniper
- FibreStore

# Tested Switches

We tested a bunch of switches, but we can't guarantee that the library works with all switches
of the same type. If you have a switch that is not listed here, please test it and let us know
if it works or not.

- FibreStore S3900
- FibreStore S5800
- Juniper EX3200
- Juniper EX3300
- Juniper EX3400
- Juniper EX4200
- Juniper EX4550
- Juniper EX4600
- Juniper QFX510002
- Juniper QFX5100-48S-8C

# Usage

All vendors implement the same interface. See vendors/drivers.go for the full
All vendors implementing the same interface. See vendors/drivers.go for the full
list of functions. The following example shows how to get the firmware version
as an example (the error handling is cut out of the example).

```go
driver, err := vendors.New(vendors.VendorFiberStore)
driver.Connect(config.Connection{
Host: "10.10.10.2",
package main

import (
"fmt"
"github.com/g-portal/switchmgr-go/pkg/config"
"github.com/g-portal/switchmgr-go/pkg/vendors"
)

func main() {
driver, err := vendors.New(vendors.VendorFiberStore)
driver.Connect(config.Connection{
Host: "10.10.10.2",
Port: 22,
Username: "admin",
Password: "admin",
})
defer driver.Disconnect()
info, err := driver.GetHardwareInfo()
fmt.println(info.FirmwareVersion)
})
defer driver.Disconnect()

info, err := driver.GetHardwareInfo()
if err != nil {
panic(err)
}

fmt.Println(info.FirmwareVersion)
}

```

## Test
Expand Down
14 changes: 10 additions & 4 deletions examples/configure-port/run.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package configure_port
package main

import (
"fmt"
Expand All @@ -11,7 +11,7 @@ import (
"strings"
)

func ConfigurePortTest() {
func main() {
driver, err := vendors.New(vendors.Vendor(os.Getenv("SWITCH_VENDOR")))
if err != nil {
log.Fatalf("Failed to create driver: %v", err)
Expand Down Expand Up @@ -78,8 +78,14 @@ func ConfigurePortTest() {
update.TaggedVLANs = taggedVLANs
}

ok, err := driver.ConfigureInterface(update)
if !ok || err != nil {
changed, err := driver.ConfigureInterface(update)
if err != nil {
log.Errorf(fmt.Sprintf("Failed to configure interface: %v", err))
}

if changed {
log.Infof("Interface %s has been configured", name)
} else {
log.Infof("Interface %s is already configured", name)
}
}
8 changes: 6 additions & 2 deletions examples/info/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,12 @@ func main() {
driver.Logger().Errorf("Failed to get interfaces: %v", err)
} else {
for _, nic := range nics {
driver.Logger().Infof("Interface: %q (%s): %s (untagged: %v, tagged: %+v, enabled: %v)",
nic.Name, nic.Description, nic.MacAddress, nic.UntaggedVLAN, nic.TaggedVLANs, nic.Enabled)
untagged := "-"
if nic.UntaggedVLAN != nil {
untagged = fmt.Sprintf("%d", *nic.UntaggedVLAN)
}
driver.Logger().Infof("Interface: %q (%s): %s (untagged: %s, tagged: %+v, enabled: %v)",
nic.Name, nic.Description, nic.MacAddress, untagged, nic.TaggedVLANs, nic.Enabled)
}
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.20
require (
github.com/Juniper/go-netconf v0.3.0
github.com/charmbracelet/log v0.2.2
github.com/google/go-cmp v0.5.8
github.com/neverlee/keymutex v0.0.0-20171121013845-f593aa834bf9
golang.org/x/crypto v0.11.0
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
Expand Down
4 changes: 2 additions & 2 deletions pkg/iosconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
func TestConfig_Interfaces(t *testing.T) {
cfg := iosconfig.Parse(utils.ReadTestData("show running-config", nil))

if len(cfg) != 56 {
t.Errorf("Config length is not 56, it is %d", len(cfg))
if len(cfg) != 46 {
t.Errorf("Config length is not 46, it is %d", len(cfg))
}

if len(cfg.Interfaces()) != 30 {
Expand Down
21 changes: 18 additions & 3 deletions pkg/vendors/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/g-portal/switchmgr-go/pkg/config"
"github.com/g-portal/switchmgr-go/pkg/models"
"github.com/g-portal/switchmgr-go/pkg/vendors/fscom"
"github.com/g-portal/switchmgr-go/pkg/vendors/fsos"
"github.com/g-portal/switchmgr-go/pkg/vendors/juniper"
"github.com/g-portal/switchmgr-go/pkg/vendors/juniper-els"
"github.com/g-portal/switchmgr-go/pkg/vendors/unimplemented"
Expand All @@ -16,8 +17,10 @@ import (
type Vendor string

const (
// VendorFiberStore FSCom (FiberStore) switches
// VendorFiberStore FSCom (FiberStore) switches, cheaper ones
VendorFiberStore Vendor = "fscom"
// VendorFSOS FSOS (FiberStore) switches, more expensive ones
VendorFSOS Vendor = "fsos"
// VendorJuniper Juniper, up to version 15 (legacy)
VendorJuniper Vendor = "juniper"
// VendorJuniperELS Juniper, version 15.1 and higher with advanced
Expand All @@ -28,7 +31,7 @@ const (
// Valid checks if this lib supports the given vendor.
func (v Vendor) Valid() bool {
switch v {
case VendorFiberStore, VendorJuniper, VendorJuniperELS:
case VendorFiberStore, VendorJuniper, VendorJuniperELS, VendorFSOS:
return true
default:
return false
Expand Down Expand Up @@ -85,11 +88,23 @@ func New(vendor Vendor) (Driver, error) {

switch vendor {
case VendorFiberStore:
return &fscom.FSCom{}, nil
return &fscom.FSCom{
LoginCommands: []string{
"enter", "terminal length 0",
},
}, nil
case VendorJuniper:
return &juniper.Juniper{}, nil
case VendorJuniperELS:
return &juniper_els.JuniperELS{}, nil
case VendorFSOS:
return &fsos.FSOS{
FSCom: fscom.FSCom{
LoginCommands: []string{
"terminal length 0",
},
},
}, nil
default:
// Should never be reached because of the Valid() check above.
return &unimplemented.Unimplemented{}, nil
Expand Down
11 changes: 10 additions & 1 deletion pkg/vendors/fscom/arp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package fscom
import (
"github.com/g-portal/switchmgr-go/pkg/models"
"github.com/g-portal/switchmgr-go/pkg/vendors/fscom/utils"
"golang.org/x/exp/slices"
"regexp"
"strings"
)

var arpLineRgx = regexp.MustCompile(`^(\d+)\s+([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4})\s+([A-Z]+)\s+(.*)$`)

func (fs *FSCom) ListArpTable() ([]models.ArpEntry, error) {
output, err := fs.sendCommands("show mac address-table")
output, err := fs.SendCommands("show mac address-table")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -44,6 +45,14 @@ func ParseArpTable(output string) ([]models.ArpEntry, error) {
continue
}

// reformat mac address
mac = models.MacAddress(mac.String())

// avoid duplicate entries
if slices.Contains(portsWithMac[matches[4]], mac) {
continue
}

portsWithMac[matches[4]] = append(portsWithMac[matches[4]], mac)
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/vendors/fscom/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,16 @@ func (cfg Configuration) ListInterfaces() ([]*models.Interface, error) {
}

// GetConfiguration returns the configuration of a FSCom switch.
func (fs *FSCom) getConfiguration() (Configuration, error) {
output, err := fs.sendCommands("show running-config")
func (fs *FSCom) GetConfiguration() (*Configuration, error) {
output, err := fs.SendCommands("show running-config")
if err != nil {
return nil, err
}

cfg := ParseConfiguration(output)
config := Configuration(cfg)

return Configuration(cfg), nil
return &config, nil
}

func (cfg Configuration) GetInterface(name string) (*models.Interface, error) {
Expand Down
21 changes: 12 additions & 9 deletions pkg/vendors/fscom/fscom.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/g-portal/switchmgr-go/pkg/config"
"github.com/g-portal/switchmgr-go/pkg/vendors/unimplemented"
"golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
"io"
"regexp"
"strings"
Expand All @@ -15,6 +16,8 @@ import (
type FSCom struct {
unimplemented.Unimplemented

LoginCommands []string

conn *ssh.Client
session *ssh.Session

Expand All @@ -34,7 +37,7 @@ func (fs *FSCom) Connect(cfg config.Connection) error {
return []string{}, err
}),
},
HostKeyAlgorithms: []string{ssh.KeyAlgoDSA},
HostKeyAlgorithms: []string{ssh.KeyAlgoDSA, ssh.KeyAlgoRSA, ssh.KeyAlgoECDSA256, ssh.KeyAlgoED25519},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

Expand Down Expand Up @@ -76,7 +79,7 @@ func (fs *FSCom) Connect(cfg config.Connection) error {
}

// entering the shell
if _, err = fs.sendCommands("enter", "terminal length 0"); err != nil {
if _, err = fs.SendCommands(fs.LoginCommands...); err != nil {
return err
}

Expand Down Expand Up @@ -104,27 +107,27 @@ func (fs *FSCom) Disconnect() error {
}

// / save saves the configuration to startup config
func (fs *FSCom) save() error {
output, err := fs.sendCommands("write")
func (fs *FSCom) Save() error {
output, err := fs.SendCommands("write")
if err != nil {
return err
}

if !strings.Contains(output, "OK!") {
if !slices.Contains([]string{"Saving current configuration...\r\n\nOK!", "Building configuration...\r\n\n[OK]"}, strings.TrimSpace(output)) {
return fmt.Errorf("failed to save the configuration: %s", output)
}

return err
}

// sendCommand sends a command to the switch and returns the output
func (fs *FSCom) sendCommands(commands ...string) (string, error) {
// SendCommands sends a command to the switch and returns the output
func (fs *FSCom) SendCommands(commands ...string) (string, error) {
output := ""

startTime := time.Now()
fs.Logger().Debugf("sendCommands %q", commands)
fs.Logger().Debugf("SendCommands %q", commands)
defer func() {
fs.Logger().Debugf("sendCommands %q took %s", commands, time.Since(startTime).String())
fs.Logger().Debugf("SendCommands %q took %s", commands, time.Since(startTime).String())
}()

reader := bufio.NewReader(fs.reader)
Expand Down
2 changes: 1 addition & 1 deletion pkg/vendors/fscom/hardware.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var versionRegex = regexp.MustCompile(`(Series\s)?Software,\sVersion\s([0-9A-Z.]
var hostnameRegex = regexp.MustCompile(`(.+)\suptime\sis\s`)

func (fs *FSCom) GetHardwareInfo() (*models.HardwareInfo, error) {
output, err := fs.sendCommands("show version")
output, err := fs.SendCommands("show version")
if err != nil {
return nil, err
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/vendors/fscom/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const (
)

func (fs *FSCom) ListInterfaces() ([]*models.Interface, error) {
config, err := fs.getConfiguration()
config, err := fs.GetConfiguration()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -100,7 +100,7 @@ func (fs *FSCom) ConfigureInterface(update *models.UpdateInterface) (bool, error

// exit interface config mode
commands = append(commands, "exit", "exit")
_, err = fs.sendCommands(commands...)
_, err = fs.SendCommands(commands...)
if err != nil {
return false, fmt.Errorf("failed to configure interface: %w", err)
}
Expand All @@ -114,7 +114,7 @@ func (fs *FSCom) ConfigureInterface(update *models.UpdateInterface) (bool, error
return false, fmt.Errorf("interface differs, original %v, updated %v", nic, update)
}

err = fs.save()
err = fs.Save()
if err != nil {
return false, err
}
Expand All @@ -123,7 +123,7 @@ func (fs *FSCom) ConfigureInterface(update *models.UpdateInterface) (bool, error
}

func (fs *FSCom) getInterfaceInfo() (map[string]fscomInterface, error) {
output, err := fs.sendCommands("show interface")
output, err := fs.SendCommands("show interface")
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/vendors/fscom/lldp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
var lldpLineRgx = regexp.MustCompile(`([a-zA-Z0-9\_]+)\s+([a-zA-Z0-9\_]+\/[0-9]+)\s`)

func (fs *FSCom) ListLLDPNeighbors() ([]models.LLDPNeighbor, error) {
output, err := fs.sendCommands("show lldp neighbors")
output, err := fs.SendCommands("show lldp neighbors")
if err != nil {
return nil, err
}
Expand Down
Loading