Refactor status functions and add first tests

This commit is contained in:
Pascal Fischer 2023-02-23 20:13:19 +01:00
parent c3ed08c249
commit e75535d30b
2 changed files with 295 additions and 130 deletions

View File

@ -2,7 +2,9 @@ package cmd
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
yaml2 "gopkg.in/yaml.v2"
"net" "net"
"net/netip" "net/netip"
"sort" "sort"
@ -21,6 +23,8 @@ import (
var ( var (
detailFlag bool detailFlag bool
ipv4Flag bool ipv4Flag bool
jsonFlag bool
yamlFlag bool
ipsFilter []string ipsFilter []string
statusFilter string statusFilter string
ipsFilterMap map[string]struct{} ipsFilterMap map[string]struct{}
@ -29,7 +33,21 @@ var (
var statusCmd = &cobra.Command{ var statusCmd = &cobra.Command{
Use: "status", Use: "status",
Short: "status of the Netbird Service", Short: "status of the Netbird Service",
RunE: func(cmd *cobra.Command, args []string) error { RunE: statusFunc,
}
func init() {
ipsFilterMap = make(map[string]struct{})
statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information in human-readable format")
statusCmd.PersistentFlags().BoolVar(&jsonFlag, "json", false, "display detailed status information in json format")
statusCmd.PersistentFlags().BoolVar(&yamlFlag, "yaml", false, "display detailed status information in yaml format")
statusCmd.PersistentFlags().BoolVar(&ipv4Flag, "ipv4", false, "display only NetBird IPv4 of this peer, e.g., --ipv4 will output 100.64.0.33")
statusCmd.MarkFlagsMutuallyExclusive("detail", "json", "yaml", "ipv4")
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200")
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected")
}
func statusFunc(cmd *cobra.Command, args []string) error {
SetFlagsFromEnvVars(rootCmd) SetFlagsFromEnvVars(rootCmd)
cmd.SetOut(cmd.OutOrStdout()) cmd.SetOut(cmd.OutOrStdout())
@ -76,18 +94,29 @@ var statusCmd = &cobra.Command{
pbFullStatus := resp.GetFullStatus() pbFullStatus := resp.GetFullStatus()
fullStatus := fromProtoFullStatus(pbFullStatus) fullStatus := fromProtoFullStatus(pbFullStatus)
cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus, resp.GetDaemonVersion(), ipv4Flag)) statusOutputString := ""
if detailFlag {
statusOutputString = parseToHumanReadable(fullStatus, daemonStatus, resp.GetDaemonVersion())
}
if jsonFlag {
statusOutputString, err = parseToJson(fullStatus)
if err != nil {
return fmt.Errorf("json marshal failed")
}
}
if yamlFlag {
statusOutputString, err = parseToYaml(fullStatus)
if err != nil {
return fmt.Errorf("yaml marshal failed")
}
}
if ipv4Flag {
statusOutputString = parseInterfaceIP(fullStatus.LocalPeerState.IP)
}
cmd.Print(statusOutputString)
return nil return nil
},
}
func init() {
ipsFilterMap = make(map[string]struct{})
statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information")
statusCmd.PersistentFlags().BoolVar(&ipv4Flag, "ipv4", false, "display only NetBird IPv4 of this peer, e.g., --ipv4 will output 100.64.0.33")
statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200")
statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected")
} }
func parseFilters() error { func parseFilters() error {
@ -148,40 +177,51 @@ func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus {
return fullStatus return fullStatus
} }
func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string, daemonVersion string, flag bool) string { func parseInterfaceIP(interfaceIP string) string {
interfaceIP := fullStatus.LocalPeerState.IP
ip, _, err := net.ParseCIDR(interfaceIP) ip, _, err := net.ParseCIDR(interfaceIP)
if err != nil { if err != nil {
return "" return ""
} }
if ipv4Flag {
return fmt.Sprintf("%s\n", ip) return fmt.Sprintf("%s\n", ip)
}
func parseToJson(fullStatus nbStatus.FullStatus) (string, error) {
jsonBytes, err := json.Marshal(fullStatus)
return string(jsonBytes), err
}
func parseToYaml(fullStatus nbStatus.FullStatus) (string, error) {
yamlBytes, err := yaml2.Marshal(fullStatus)
return string(yamlBytes), err
}
func countConnectedPeers(peers []nbStatus.PeerState) int {
peersConnected := 0
for _, peerState := range peers {
if peerState.ConnStatus == peer.StatusConnected.String() {
peersConnected = peersConnected + 1
} }
var (
managementStatusURL = ""
signalStatusURL = ""
managementConnString = "Disconnected"
signalConnString = "Disconnected"
interfaceTypeString = "Userspace"
)
if printDetail {
managementStatusURL = fmt.Sprintf(" to %s", fullStatus.ManagementState.URL)
signalStatusURL = fmt.Sprintf(" to %s", fullStatus.SignalState.URL)
} }
return peersConnected
}
func parseGeneralSummary(fullStatus nbStatus.FullStatus, daemonStatus string, daemonVersion string) string {
managementStatusURL := fmt.Sprintf(" to %s", fullStatus.ManagementState.URL)
signalStatusURL := fmt.Sprintf(" to %s", fullStatus.SignalState.URL)
managementConnString := "Disconnected"
if fullStatus.ManagementState.Connected { if fullStatus.ManagementState.Connected {
managementConnString = "Connected" managementConnString = "Connected"
} }
signalConnString := "Disconnected"
if fullStatus.SignalState.Connected { if fullStatus.SignalState.Connected {
signalConnString = "Connected" signalConnString = "Connected"
} }
interfaceTypeString := "Userspace"
interfaceIP := ""
if fullStatus.LocalPeerState.KernelInterface { if fullStatus.LocalPeerState.KernelInterface {
interfaceTypeString = "Kernel" interfaceTypeString = "Kernel"
} else if fullStatus.LocalPeerState.IP == "" { } else if fullStatus.LocalPeerState.IP == "" {
@ -189,8 +229,7 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
interfaceIP = "N/A" interfaceIP = "N/A"
} }
parsedPeersString, peersConnected := parsePeers(fullStatus.Peers, printDetail) peersConnected := countConnectedPeers(fullStatus.Peers)
peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers)) peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers))
summary := fmt.Sprintf( summary := fmt.Sprintf(
@ -215,8 +254,13 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
interfaceTypeString, interfaceTypeString,
peersCountString, peersCountString,
) )
return summary
}
func parseToHumanReadable(fullStatus nbStatus.FullStatus, daemonStatus string, daemonVersion string) string {
parsedPeersString := parsePeers(fullStatus.Peers)
summary := parseGeneralSummary(fullStatus, daemonStatus, daemonVersion)
if printDetail {
return fmt.Sprintf( return fmt.Sprintf(
"Peers detail:"+ "Peers detail:"+
"%s\n"+ "%s\n"+
@ -224,14 +268,11 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
parsedPeersString, parsedPeersString,
summary, summary,
) )
}
return summary
} }
func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) { func parsePeers(peers []nbStatus.PeerState) string {
var ( var (
peersString = "" peersString = ""
peersConnected = 0
) )
if len(peers) > 0 { if len(peers) > 0 {
@ -242,17 +283,8 @@ func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) {
}) })
} }
connectedStatusString := peer.StatusConnected.String()
for _, peerState := range peers { for _, peerState := range peers {
peerConnectionStatus := false peerConnectionStatus := peerState.ConnStatus == peer.StatusConnected.String()
if peerState.ConnStatus == connectedStatusString {
peersConnected = peersConnected + 1
peerConnectionStatus = true
}
if printDetail {
if skipDetailByFilters(peerState, peerConnectionStatus) { if skipDetailByFilters(peerState, peerConnectionStatus) {
continue continue
} }
@ -293,8 +325,7 @@ func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) {
peersString = peersString + peerString peersString = peersString + peerString
} }
} return peersString
return peersString, peersConnected
} }
func skipDetailByFilters(peerState nbStatus.PeerState, isConnected bool) bool { func skipDetailByFilters(peerState nbStatus.PeerState, isConnected bool) bool {

134
client/cmd/status_test.go Normal file
View File

@ -0,0 +1,134 @@
package cmd
import (
nbStatus "github.com/netbirdio/netbird/client/status"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
var fullStatus = nbStatus.FullStatus{
Peers: []nbStatus.PeerState{
{
IP: "192.168.178.101",
PubKey: "Pubkey1",
FQDN: "peer-1.awesome-domain.com",
ConnStatus: "Connected",
ConnStatusUpdate: time.Date(2001, time.Month(1), 1, 1, 1, 1, 0, time.UTC),
Relayed: false,
LocalIceCandidateType: "-",
RemoteIceCandidateType: "-",
},
{
IP: "192.168.178.102",
PubKey: "Pubkey2",
FQDN: "peer-2.awesome-domain.com",
ConnStatus: "Connected",
ConnStatusUpdate: time.Date(2002, time.Month(2), 2, 2, 2, 2, 0, time.UTC),
Relayed: false,
LocalIceCandidateType: "-",
RemoteIceCandidateType: "-",
},
},
ManagementState: nbStatus.ManagementState{
URL: "my-awesome-management.com:443",
Connected: true,
},
SignalState: nbStatus.SignalState{
URL: "my-awesome-signal.com:443",
Connected: true,
},
LocalPeerState: nbStatus.LocalPeerState{
IP: "192.168.178.2",
PubKey: "Some-Pub-Key",
KernelInterface: false,
FQDN: "some-localhost.awesome-domain.com",
},
}
// @formatter:off
func TestParsingToJson(t *testing.T) {
json, _ := parseToJson(fullStatus)
expectedJson := "{" +
"\"Peers\":" +
"[" +
"{" +
"\"IP\":\"192.168.178.101\"," +
"\"PubKey\":\"Pubkey1\"," +
"\"FQDN\":\"peer-1.awesome-domain.com\"," +
"\"ConnStatus\":\"Connected\"," +
"\"ConnStatusUpdate\":\"2001-01-01T01:01:01Z\"," +
"\"Relayed\":false," +
"\"Direct\":false," +
"\"LocalIceCandidateType\":\"-\"," +
"\"RemoteIceCandidateType\":\"-\"" +
"}," +
"{" +
"\"IP\":\"192.168.178.102\"," +
"\"PubKey\":\"Pubkey2\"," +
"\"FQDN\":\"peer-2.awesome-domain.com\"," +
"\"ConnStatus\":\"Connected\"," +
"\"ConnStatusUpdate\":\"2002-02-02T02:02:02Z\"," +
"\"Relayed\":false," +
"\"Direct\":false," +
"\"LocalIceCandidateType\":\"-\"," +
"\"RemoteIceCandidateType\":\"-\"" +
"}" +
"]," +
"\"ManagementState\":" +
"{" +
"\"URL\":\"my-awesome-management.com:443\"," +
"\"Connected\":true" +
"}," +
"\"SignalState\":" +
"{" +
"\"URL\":\"my-awesome-signal.com:443\"," +
"\"Connected\":true" +
"}," +
"\"LocalPeerState\":" +
"{" +
"\"IP\":\"192.168.178.2\"," +
"\"PubKey\":\"Some-Pub-Key\"," +
"\"KernelInterface\":false," +
"\"FQDN\":\"some-localhost.awesome-domain.com\"" +
"}" +
"}"
assert.Equal(t, expectedJson, json)
}
func TestParsingToYaml(t *testing.T) {
yaml, _ := parseToYaml(fullStatus)
expectedYaml := "peers:\n" +
"- ip: 192.168.178.101\n" +
" pubkey: Pubkey1\n" +
" fqdn: peer-1.awesome-domain.com\n" +
" connstatus: Connected\n" +
" connstatusupdate: 2001-01-01T01:01:01Z\n" +
" relayed: false\n" +
" direct: false\n" +
" localicecandidatetype: '-'\n" +
" remoteicecandidatetype: '-'\n" +
"- ip: 192.168.178.102\n" +
" pubkey: Pubkey2\n" +
" fqdn: peer-2.awesome-domain.com\n" +
" connstatus: Connected\n" +
" connstatusupdate: 2002-02-02T02:02:02Z\n" +
" relayed: false\n" +
" direct: false\n" +
" localicecandidatetype: '-'\n" +
" remoteicecandidatetype: '-'\n" +
"managementstate:\n" +
" url: my-awesome-management.com:443\n" +
" connected: true\n" +
"signalstate:\n" +
" url: my-awesome-signal.com:443\n" +
" connected: true\n" +
"localpeerstate:\n" +
" ip: 192.168.178.2\n" +
" pubkey: Some-Pub-Key\n" +
" kernelinterface: false\n" +
" fqdn: some-localhost.awesome-domain.com\n"
assert.Equal(t, expectedYaml, yaml)
}