Skip to content

Commit

Permalink
code updates - splitting formatters for convenience and group peers b…
Browse files Browse the repository at this point in the history
…y ns for dot
  • Loading branch information
shireenf-ibm committed Nov 27, 2023
1 parent c9e1d07 commit 8235e39
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 135 deletions.
135 changes: 0 additions & 135 deletions pkg/netpol/connlist/conns_formatter.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package connlist

import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"sort"
"strings"

"github.com/np-guard/netpol-analyzer/pkg/netpol/eval"
"github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common"
)

Expand Down Expand Up @@ -52,133 +47,3 @@ func formSingleConn(conn Peer2PeerConnection) singleConnFields {
connStr := common.ConnStrFromConnProperties(conn.AllProtocolsAndPorts(), conn.ProtocolsAndPorts())
return singleConnFields{Src: conn.Src().String(), Dst: conn.Dst().String(), ConnString: connStr}
}

// formatText: implements the connsFormatter interface for txt output format
type formatText struct {
}

// returns a textual string format of connections from list of Peer2PeerConnection objects
func (t formatText) writeOutput(conns []Peer2PeerConnection) (string, error) {
connLines := make([]string, len(conns))
for i := range conns {
connLines[i] = formSingleConn(conns[i]).string()
}
sort.Strings(connLines)
return strings.Join(connLines, newLineChar), nil
}

// formatJSON: implements the connsFormatter interface for JSON output format
type formatJSON struct {
}

// returns a json string form of connections from list of Peer2PeerConnection objects
func (j formatJSON) writeOutput(conns []Peer2PeerConnection) (string, error) {
// get an array of sorted conns items ([]singleConnFields)
sortedConnItems := sortConnections(conns)
jsonConns, err := json.MarshalIndent(sortedConnItems, "", " ")
if err != nil {
return "", err
}
return string(jsonConns), nil
}

// formatDOT: implements the connsFormatter interface for dot output format
type formatDOT struct {
}

// formats an edge line from a singleConnFields struct , to be used for dot graph
func getEdgeLine(c singleConnFields) string {
return fmt.Sprintf("\t%q -> %q [label=%q color=\"gold2\" fontcolor=\"darkgreen\"]", c.Src, c.Dst, c.ConnString)
}

// formats a peer line for dot graph
func getPeerLine(peer eval.Peer) string {
var peerColor string
if peer.IsPeerIPType() {
peerColor = "red2"
} else {
peerColor = "blue"
}
peerName := peer.String()
return fmt.Sprintf("\t%q [label=%q color=%q fontcolor=%q]", peerName, peerName, peerColor, peerColor)
}

// returns a dot string form of connections from list of Peer2PeerConnection objects
func (d formatDOT) writeOutput(conns []Peer2PeerConnection) (string, error) {
edgeLines := make([]string, len(conns)) // list of edges lines
peersVisited := make(map[string]struct{}, 0) // acts as a set
peerLines := make([]string, 0) // list of peers lines
for index := range conns {
connLine := formSingleConn(conns[index])
edgeLines[index] = getEdgeLine(connLine)
if _, ok := peersVisited[connLine.Src]; !ok {
peersVisited[connLine.Src] = struct{}{}
peerLines = append(peerLines, getPeerLine(conns[index].Src()))
}
if _, ok := peersVisited[connLine.Dst]; !ok {
peersVisited[connLine.Dst] = struct{}{}
peerLines = append(peerLines, getPeerLine(conns[index].Dst()))
}
}
// sort graph lines
sort.Strings(peerLines)
sort.Strings(edgeLines)
// collect all lines by order
allLines := []string{common.DotHeader}
allLines = append(allLines, peerLines...)
allLines = append(allLines, edgeLines...)
allLines = append(allLines, common.DotClosing)
return strings.Join(allLines, newLineChar), nil
}

// formatCSV: implements the connsFormatter interface for csv output format
type formatCSV struct {
}

// returns a CSV string form of connections from list of Peer2PeerConnection objects
func (cs formatCSV) writeOutput(conns []Peer2PeerConnection) (string, error) {
// get an array of sorted conns items ([]singleConnFields)
sortedConnItems := sortConnections(conns)
var headerCSV = []string{"src", "dst", "conn"}

// writing csv rows into a buffer
buf := new(bytes.Buffer)
writer := csv.NewWriter(buf)
if err := writer.Write(headerCSV); err != nil {
return "", err
}
for _, conn := range sortedConnItems {
row := []string{conn.Src, conn.Dst, conn.ConnString}
if err := writer.Write(row); err != nil {
return "", err
}
}
writer.Flush()
return buf.String(), nil
}

// formatMD: implements the connsFormatter interface for md output format
type formatMD struct {
}

// formats the md output header
func getMDHeader() string {
return "| src | dst | conn |\n|-----|-----|------|"
}

// formats a connection line for md output
func getMDLine(c singleConnFields) string {
return fmt.Sprintf("| %s | %s | %s |", c.Src, c.Dst, c.ConnString)
}

// returns a md string form of connections from list of Peer2PeerConnection objects
func (md formatMD) writeOutput(conns []Peer2PeerConnection) (string, error) {
mdLines := make([]string, len(conns))
for index := range conns {
mdLines[index] = getMDLine(formSingleConn(conns[index]))
}
sort.Strings(mdLines)
allLines := []string{getMDHeader()}
allLines = append(allLines, mdLines...)
return strings.Join(allLines, newLineChar), nil
}
32 changes: 32 additions & 0 deletions pkg/netpol/connlist/conns_formatter_csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package connlist

import (
"bytes"
"encoding/csv"
)

// formatCSV: implements the connsFormatter interface for csv output format
type formatCSV struct {
}

// returns a CSV string form of connections from list of Peer2PeerConnection objects
func (cs formatCSV) writeOutput(conns []Peer2PeerConnection) (string, error) {
// get an array of sorted conns items ([]singleConnFields)
sortedConnItems := sortConnections(conns)
var headerCSV = []string{"src", "dst", "conn"}

// writing csv rows into a buffer
buf := new(bytes.Buffer)
writer := csv.NewWriter(buf)
if err := writer.Write(headerCSV); err != nil {
return "", err
}
for _, conn := range sortedConnItems {
row := []string{conn.Src, conn.Dst, conn.ConnString}
if err := writer.Write(row); err != nil {
return "", err
}
}
writer.Flush()
return buf.String(), nil
}
103 changes: 103 additions & 0 deletions pkg/netpol/connlist/conns_formatter_dot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package connlist

import (
"fmt"
"sort"
"strings"

"github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common"
)

const (
ipColor = "red2"
nsPeerColor = "blue"
)

// formatDOT: implements the connsFormatter interface for dot output format
type formatDOT struct {
}

// formats an edge line from a singleConnFields struct , to be used for dot graph
func getEdgeLine(c Peer2PeerConnection) string {
connStr := common.ConnStrFromConnProperties(c.AllProtocolsAndPorts(), c.ProtocolsAndPorts())
srcName, _ := peerNameAndColorByType(c.Src())
dstName, _ := peerNameAndColorByType(c.Dst())
return fmt.Sprintf("\t%q -> %q [label=%q color=\"gold2\" fontcolor=\"darkgreen\"]", srcName, dstName, connStr)
}

func peerNameAndColorByType(peer Peer) (name, color string) {
if peer.IsPeerIPType() {
return peer.String(), ipColor
} else if peer.Name() == common.IngressPodName {
return peer.String(), nsPeerColor
}
return peer.Name(), nsPeerColor
}

// formats a peer line for dot graph
func getPeerLine(peer Peer) string {
peerName, peerColor := peerNameAndColorByType(peer)
return fmt.Sprintf("\t\t%q [label=%q color=%q fontcolor=%q]", peerName, peerName, peerColor, peerColor)
}

// returns a dot string form of connections from list of Peer2PeerConnection objects
func (d formatDOT) writeOutput(conns []Peer2PeerConnection) (string, error) {
nsPeers := make(map[string][]string) // map from namespace to its peers (grouping peers by namespaces)
edgeLines := make([]string, len(conns)) // list of edges lines
peersVisited := make(map[string]struct{}, 0) // acts as a set
for index := range conns {
srcStr, dstStr := conns[index].Src().String(), conns[index].Dst().String()
edgeLines[index] = getEdgeLine(conns[index])
if _, ok := peersVisited[srcStr]; !ok {
peersVisited[srcStr] = struct{}{}
checkAndAddPeerToNsGroup(nsPeers, conns[index].Src())
}
if _, ok := peersVisited[dstStr]; !ok {
peersVisited[dstStr] = struct{}{}
checkAndAddPeerToNsGroup(nsPeers, conns[index].Dst())
}
}
// sort graph lines
sort.Strings(edgeLines)
// collect all lines by order
allLines := []string{common.DotHeader}
allLines = append(allLines, addNsGroups(nsPeers)...)
allLines = append(allLines, edgeLines...)
allLines = append(allLines, common.DotClosing)
return strings.Join(allLines, newLineChar), nil
}

func checkAndAddPeerToNsGroup(mapNsToPeers map[string][]string, peer Peer) {
if _, ok := mapNsToPeers[peer.Namespace()]; !ok {
mapNsToPeers[peer.Namespace()] = []string{}
}
mapNsToPeers[peer.Namespace()] = append(mapNsToPeers[peer.Namespace()], getPeerLine(peer))
}

func addNsGroups(nsPeersMap map[string][]string) []string {
res := []string{}
// sort namespaces (map's keys) to ensure same output always
nsKeys := sortMapKeys(nsPeersMap)
// write ns groups
for _, ns := range nsKeys {
peersLines := nsPeersMap[ns]
sort.Strings(peersLines)
// create ns subgraph cluster
nsLabel := strings.ReplaceAll(ns, "-", "_")
nsLines := []string{"\tsubgraph cluster_" + nsLabel + " {"} // subgraph header
nsLines = append(nsLines, peersLines...)
nsLines = append(nsLines, "\t\tlabel=\""+ns+"\"", "\t}")
// add ns section to the res
res = append(res, nsLines...)
}
return res
}

func sortMapKeys(nsPeersMap map[string][]string) []string {
keys := make([]string, 0, len(nsPeersMap))
for k := range nsPeersMap {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
18 changes: 18 additions & 0 deletions pkg/netpol/connlist/conns_formatter_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package connlist

import "encoding/json"

// formatJSON: implements the connsFormatter interface for JSON output format
type formatJSON struct {
}

// returns a json string form of connections from list of Peer2PeerConnection objects
func (j formatJSON) writeOutput(conns []Peer2PeerConnection) (string, error) {
// get an array of sorted conns items ([]singleConnFields)
sortedConnItems := sortConnections(conns)
jsonConns, err := json.MarshalIndent(sortedConnItems, "", " ")
if err != nil {
return "", err
}
return string(jsonConns), nil
}
33 changes: 33 additions & 0 deletions pkg/netpol/connlist/conns_formatter_md.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package connlist

import (
"fmt"
"sort"
"strings"
)

// formatMD: implements the connsFormatter interface for md output format
type formatMD struct {
}

// formats the md output header
func getMDHeader() string {
return "| src | dst | conn |\n|-----|-----|------|"
}

// formats a connection line for md output
func getMDLine(c singleConnFields) string {
return fmt.Sprintf("| %s | %s | %s |", c.Src, c.Dst, c.ConnString)
}

// returns a md string form of connections from list of Peer2PeerConnection objects
func (md formatMD) writeOutput(conns []Peer2PeerConnection) (string, error) {
mdLines := make([]string, len(conns))
for index := range conns {
mdLines[index] = getMDLine(formSingleConn(conns[index]))
}
sort.Strings(mdLines)
allLines := []string{getMDHeader()}
allLines = append(allLines, mdLines...)
return strings.Join(allLines, newLineChar), nil
}
20 changes: 20 additions & 0 deletions pkg/netpol/connlist/conns_formatter_txt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package connlist

import (
"sort"
"strings"
)

// formatText: implements the connsFormatter interface for txt output format
type formatText struct {
}

// returns a textual string format of connections from list of Peer2PeerConnection objects
func (t formatText) writeOutput(conns []Peer2PeerConnection) (string, error) {
connLines := make([]string, len(conns))
for i := range conns {
connLines[i] = formSingleConn(conns[i]).string()
}
sort.Strings(connLines)
return strings.Join(connLines, newLineChar), nil
}

0 comments on commit 8235e39

Please sign in to comment.