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

Generate the communication matrix in nftables format #14

Merged
merged 5 commits into from
Jul 23, 2024
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ For example, consider the following ss entry:
LISTEN 0 4096 127.0.0.1:10248 0.0.0.0:* users:(("kubelet",pid=6187,fd=20))
```

The `ss` package provides the `CreateComDetailsFromNode` function that runs
The `ss` package provides the `CreateSSOutputFromNode` function that runs
the `ss` command on each node, and converts the output into a corresponding ComDetails list.

### Communication Matrix Creation Guide
Expand All @@ -31,7 +31,7 @@ Examples are available in the `example-custom-entries` files.

The following environment variables are used to configure:
```
FORMAT (csv/json/yaml)
FORMAT (csv/json/yaml/nft)
CLUSTER_ENV (baremetal/cloud)
DEST_DIR (path to the directory containing the artifacts)
DEPLOYMENT (mno/sno)
Expand Down
183 changes: 31 additions & 152 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,34 @@
package main

import (
"context"
"flag"
"fmt"
"os"
"path"
"path/filepath"
"sync"

"golang.org/x/sync/errgroup"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

clientutil "github.com/openshift-kni/commatrix/client"
"github.com/openshift-kni/commatrix/commatrix"
"github.com/openshift-kni/commatrix/consts"
"github.com/openshift-kni/commatrix/debug"
"github.com/openshift-kni/commatrix/ss"
"github.com/openshift-kni/commatrix/types"
)

func main() {
var (
destDir string
format string
envStr string
deploymentStr string
customEntriesPath string
customEntriesFormat string
printFn func(m types.ComMatrix) ([]byte, error)
)
var (
destDir string
format string
envStr string
deploymentStr string
customEntriesPath string
customEntriesFormat string
)

func init() {
flag.StringVar(&destDir, "destDir", "communication-matrix", "Output files dir")
flag.StringVar(&format, "format", "csv", "Desired format (json,yaml,csv)")
flag.StringVar(&format, "format", "csv", "Desired format (json,yaml,csv,nft)")
flag.StringVar(&envStr, "env", "baremetal", "Cluster environment (baremetal/cloud)")
flag.StringVar(&deploymentStr, "deployment", "mno", "Deployment type (mno/sno)")
flag.StringVar(&customEntriesPath, "customEntriesPath", "", "Add custom entries from a file to the matrix")
flag.StringVar(&customEntriesFormat, "customEntriesFormat", "", "Set the format of the custom entries file (json,yaml,csv)")

flag.Parse()
}

switch format {
case "json":
printFn = types.ToJSON
case "csv":
printFn = types.ToCSV
case "yaml":
printFn = types.ToYAML
default:
panic(fmt.Sprintf("invalid format: %s. Please specify json, csv, or yaml.", format))
}

func main() {
kubeconfig, ok := os.LookupEnv("KUBECONFIG")
if !ok {
panic("must set the KUBECONFIG environment variable")
Expand Down Expand Up @@ -79,136 +57,37 @@ func main() {
if customEntriesPath != "" && customEntriesFormat == "" {
panic("error, variable customEntriesFormat is not set")
}

// generate the endpointslice matrix
mat, err := commatrix.New(kubeconfig, customEntriesPath, customEntriesFormat, env, deployment)
if err != nil {
panic(fmt.Sprintf("failed to create the communication matrix: %s", err))
}

res, err := printFn(*mat)
if err != nil {
panic(err)
}

comMatrixFileName := filepath.Join(destDir, fmt.Sprintf("communication-matrix.%s", format))
err = os.WriteFile(comMatrixFileName, res, 0644)
if err != nil {
panic(err)
}

cs, err := clientutil.New(kubeconfig)
if err != nil {
panic(err)
}

tcpFile, err := os.OpenFile(path.Join(destDir, "raw-ss-tcp"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer tcpFile.Close()

udpFile, err := os.OpenFile(path.Join(destDir, "raw-ss-udp"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
panic(fmt.Errorf("failed to create the communication matrix: %s", err))
}
defer udpFile.Close()

nodesList, err := cs.CoreV1Interface.Nodes().List(context.TODO(), metav1.ListOptions{})
// write the endpoint slice matrix to file
err = commatrix.WriteMatrixToFileByType(*mat, "communication-matrix", format, deployment, destDir)
if err != nil {
panic(err)
panic(fmt.Sprintf("Error while writing the endpoint slice matrix to file :%v", err))
}

nodesComDetails := []types.ComDetails{}

err = debug.CreateNamespace(cs, consts.DefaultDebugNamespace)
// generate the ss matrix and ss raws
ssMat, ssOutTCP, ssOutUDP, err := commatrix.GenerateSS(kubeconfig, customEntriesPath, customEntriesFormat, format, env, deployment, destDir)
if err != nil {
panic(err)
}
defer func() {
err := debug.DeleteNamespace(cs, consts.DefaultDebugNamespace)
if err != nil {
panic(err)
}
}()

nLock := &sync.Mutex{}
g := new(errgroup.Group)
for _, n := range nodesList.Items {
node := n
g.Go(func() error {
debugPod, err := debug.New(cs, node.Name, consts.DefaultDebugNamespace, consts.DefaultDebugPodImage)
if err != nil {
return err
}
defer func() {
err := debugPod.Clean()
if err != nil {
fmt.Printf("failed cleaning debug pod %s: %v", debugPod, err)
}
}()

cds, err := ss.CreateComDetailsFromNode(debugPod, &node, tcpFile, udpFile)
if err != nil {
return err
}
nLock.Lock()
nodesComDetails = append(nodesComDetails, cds...)
nLock.Unlock()
return nil
})
panic(fmt.Sprintf("Error while generating the ss matrix and ss raws :%v", err))
}

err = g.Wait()
// write ss raw files
err = commatrix.WriteSSRawFiles(destDir, ssOutTCP, ssOutUDP)
if err != nil {
panic(err)
panic(fmt.Sprintf("Error while writing the ss raw files :%v", err))
}

cleanedComDetails := types.CleanComDetails(nodesComDetails)
ssComMat := types.ComMatrix{Matrix: cleanedComDetails}

res, err = printFn(ssComMat)
// write the ss matrix to file
err = commatrix.WriteMatrixToFileByType(*ssMat, "ss-generated-matrix", format, deployment, destDir)
if err != nil {
panic(err)
panic(fmt.Sprintf("Error while writing ss matrix to file :%v", err))
}
// generate the diff matrix between the enpointslice and the ss matrix
diff := commatrix.GenerateMatrixDiff(*mat, *ssMat)

ssMatrixFileName := filepath.Join(destDir, fmt.Sprintf("ss-generated-matrix.%s", format))
err = os.WriteFile(ssMatrixFileName, res, 0644)
// write the diff matrix between the enpointslice and the ss matrix to file
err = os.WriteFile(filepath.Join(destDir, "matrix-diff-ss"), []byte(diff), 0644)
if err != nil {
panic(err)
panic(fmt.Sprintf("Error writing the diff matrix :%v", err))
}

diff := buildMatrixDiff(*mat, ssComMat)

err = os.WriteFile(filepath.Join(destDir, "matrix-diff-ss"),
[]byte(diff),
0644)
if err != nil {
panic(err)
}
}

func buildMatrixDiff(mat1 types.ComMatrix, mat2 types.ComMatrix) string {
diff := consts.CSVHeaders + "\n"
for _, cd := range mat1.Matrix {
if mat2.Contains(cd) {
diff += fmt.Sprintf("%s\n", cd)
continue
}

diff += fmt.Sprintf("+ %s\n", cd)
}

for _, cd := range mat2.Matrix {
// Skip "rpc.statd" ports, these are randomly open ports on the node,
// no need to mention them in the matrix diff
if cd.Service == "rpc.statd" {
continue
}

if !mat1.Contains(cd) {
diff += fmt.Sprintf("- %s\n", cd)
}
}

return diff
}
52 changes: 52 additions & 0 deletions commatrix/commatrix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package commatrix

import (
"fmt"
"path/filepath"
"testing"

"github.com/openshift-kni/commatrix/types"
"github.com/stretchr/testify/assert"
)

func TestGetPrintFunction(t *testing.T) {
tests := []struct {
format string
expectedFnType string
expectedErr bool
}{
{"json", "func(types.ComMatrix) ([]uint8, error)", false},
{"csv", "func(types.ComMatrix) ([]uint8, error)", false},
{"yaml", "func(types.ComMatrix) ([]uint8, error)", false},
{"nft", "func(types.ComMatrix) ([]uint8, error)", false},
{"invalid", "", true},
}

for _, tt := range tests {
t.Run(tt.format, func(t *testing.T) {
fn, err := getPrintFunction(tt.format)
if tt.expectedErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, fmt.Sprintf("%T", fn), tt.expectedFnType)
}
})
}
}

func TestWriteMatrixToFile(t *testing.T) {
destDir := t.TempDir()
matrix := types.ComMatrix{
Matrix: []types.ComDetails{
{NodeRole: "master", Service: "testService"},
},
}
printFn := types.ToJSON
fileName := "test-matrix"
format := "json"

err := writeMatrixToFile(matrix, fileName, format, printFn, destDir)
assert.NoError(t, err)
assert.FileExists(t, filepath.Join(destDir, "test-matrix.json"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't do much, wdyt about expanding the test to a table (with all formats) and actually checking the content of the files? you can probably use what we did on metallb with the .golden files.

}
Loading
Loading