Skip to content

Commit

Permalink
Testing for the analyzer package + small refactoring (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
zivnevo committed Jun 26, 2023
1 parent 938039f commit 4d5493a
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 36 deletions.
9 changes: 5 additions & 4 deletions pkg/analyzer/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ SPDX-License-Identifier: Apache-2.0
package analyzer

import (
"io"
"bytes"

"k8s.io/apimachinery/pkg/util/yaml"
)

const yamlParseBufferSize = 200

func parseResource[T interface{}](r io.Reader) *T {
if r == nil {
func parseResource[T interface{}](objDataBuf []byte) *T {
reader := bytes.NewReader(objDataBuf)
if reader == nil {
return nil
}
var rc T
err := yaml.NewYAMLOrJSONDecoder(r, yamlParseBufferSize).Decode(&rc)
err := yaml.NewYAMLOrJSONDecoder(reader, yamlParseBufferSize).Decode(&rc)
if err != nil {
return nil
}
Expand Down
43 changes: 15 additions & 28 deletions pkg/analyzer/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ SPDX-License-Identifier: Apache-2.0
package analyzer

import (
"bytes"
"fmt"
"net/url"
"strconv"
Expand All @@ -32,27 +31,27 @@ func ScanK8sWorkloadObject(kind string, objDataBuf []byte) (*common.Resource, er
resourceCtx.Resource.Kind = kind
switch kind { // TODO: handle Pod
case "ReplicaSet":
obj := parseResource[appsv1.ReplicaSet](bytes.NewReader(objDataBuf))
obj := parseResource[appsv1.ReplicaSet](objDataBuf)
podSpecV1 = &obj.Spec.Template
metaObj = obj
case "ReplicationController":
obj := parseResource[v1.ReplicationController](bytes.NewReader(objDataBuf))
obj := parseResource[v1.ReplicationController](objDataBuf)
podSpecV1 = obj.Spec.Template
metaObj = obj
case "Deployment":
obj := parseResource[appsv1.Deployment](bytes.NewReader(objDataBuf))
obj := parseResource[appsv1.Deployment](objDataBuf)
podSpecV1 = &obj.Spec.Template
metaObj = obj
case "DaemonSet":
obj := parseResource[appsv1.DaemonSet](bytes.NewReader(objDataBuf))
obj := parseResource[appsv1.DaemonSet](objDataBuf)
podSpecV1 = &obj.Spec.Template
metaObj = obj
case "StatefulSet":
obj := parseResource[appsv1.StatefulSet](bytes.NewReader(objDataBuf))
obj := parseResource[appsv1.StatefulSet](objDataBuf)
podSpecV1 = &obj.Spec.Template
metaObj = obj
case "Job":
obj := parseResource[batchv1.Job](bytes.NewReader(objDataBuf))
obj := parseResource[batchv1.Job](objDataBuf)
podSpecV1 = &obj.Spec.Template
metaObj = obj
default:
Expand All @@ -71,8 +70,8 @@ func matchLabelSelectorToStrLabels(labels map[string]string) []string {
return res
}

func ScanK8sConfigmapObject(kind string, objDataBuf []byte) (*common.CfgMap, error) {
obj := parseResource[v1.ConfigMap](bytes.NewReader(objDataBuf))
func ScanK8sConfigmapObject(objDataBuf []byte) (*common.CfgMap, error) {
obj := parseResource[v1.ConfigMap](objDataBuf)
if obj == nil {
return nil, fmt.Errorf("unable to parse configmap")
}
Expand All @@ -82,19 +81,15 @@ func ScanK8sConfigmapObject(kind string, objDataBuf []byte) (*common.CfgMap, err
}

// Create a common.Service object from a k8s Service object
func ScanK8sServiceObject(kind string, objDataBuf []byte) (*common.Service, error) {
if kind != "Service" {
return nil, fmt.Errorf("expected parsing a Service resource, but got `%s`", kind)
}

svcObj := parseResource[v1.Service](bytes.NewReader(objDataBuf))
func ScanK8sServiceObject(objDataBuf []byte) (*common.Service, error) {
svcObj := parseResource[v1.Service](objDataBuf)
if svcObj == nil {
return nil, fmt.Errorf("failed to parse Service resource")
}
var serviceCtx common.Service
serviceCtx.Resource.Name = svcObj.GetName()
serviceCtx.Resource.Namespace = svcObj.Namespace
serviceCtx.Resource.Kind = kind
serviceCtx.Resource.Kind = svcObj.Kind
serviceCtx.Resource.Type = svcObj.Spec.Type
serviceCtx.Resource.Selectors = matchLabelSelectorToStrLabels(svcObj.Spec.Selector)
serviceCtx.Resource.ExposeExternally = (svcObj.Spec.Type == v1.ServiceTypeLoadBalancer || svcObj.Spec.Type == v1.ServiceTypeNodePort)
Expand All @@ -109,12 +104,8 @@ func ScanK8sServiceObject(kind string, objDataBuf []byte) (*common.Service, erro
}

// Scan an OpenShift Route object and mark the services it uses to be exposed inside the cluster
func ScanOCRouteObject(kind string, objDataBuf []byte, servicesToExpose common.ServicesToExpose) error {
if kind != "Route" {
return fmt.Errorf("expected parsing a Route resource, but got `%s`", kind)
}

routeObj := parseResource[ocroutev1.Route](bytes.NewReader(objDataBuf))
func ScanOCRouteObject(objDataBuf []byte, servicesToExpose common.ServicesToExpose) error {
routeObj := parseResource[ocroutev1.Route](objDataBuf)
if routeObj == nil {
return fmt.Errorf("failed to parse Route resource")
}
Expand All @@ -133,12 +124,8 @@ func ScanOCRouteObject(kind string, objDataBuf []byte, servicesToExpose common.S
}

// Scan an Ingress object and mark the services it uses to be exposed inside the cluster
func ScanIngressObject(kind string, objDataBuf []byte, servicesToExpose common.ServicesToExpose) error {
if kind != "Ingress" {
return fmt.Errorf("expected parsing a Ingress resource, but got `%s`", kind)
}

ingressObj := parseResource[networkv1.Ingress](bytes.NewReader(objDataBuf))
func ScanIngressObject(objDataBuf []byte, servicesToExpose common.ServicesToExpose) error {
ingressObj := parseResource[networkv1.Ingress](objDataBuf)
if ingressObj == nil {
return fmt.Errorf("failed to parse Ingress resource")
}
Expand Down
123 changes: 123 additions & 0 deletions pkg/analyzer/scan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package analyzer

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/np-guard/cluster-topology-analyzer/pkg/common"
)

func TestNetworkAddressValue(t *testing.T) {
type strBoolPair struct {
str string
b bool
}

valuesToCheck := map[string]strBoolPair{
"svc": {"svc", true},
"svc:500": {"svc:500", true},
"http://svc:500": {"svc:500", true},
"fttps://svc:500/something#abc": {"svc:500", true},
strings.Repeat("abc", 500): {"", false},
"not%a*url": {"", false},
"123": {"", false},
}

for val, expectedAnswer := range valuesToCheck {
strRes, boolRes := NetworkAddressValue(val)
require.Equal(t, expectedAnswer.b, boolRes)
require.Equal(t, expectedAnswer.str, strRes)
}
}

func TestScanningSvc(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"k8s_guestbook", "frontend-service.yaml"})
require.Nil(t, err)
res, err := ScanK8sServiceObject(resourceBuf)
require.Nil(t, err)
require.Equal(t, "frontend", res.Resource.Name)
require.Len(t, res.Resource.Selectors, 2)
require.Len(t, res.Resource.Network, 1)
require.Equal(t, 80, res.Resource.Network[0].Port)
}

func TestScanningDeploymentWithArgs(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"sockshop", "manifests", "01-carts-dep.yaml"})
require.Nil(t, err)
res, err := ScanK8sWorkloadObject("Deployment", resourceBuf)
require.Nil(t, err)
require.Equal(t, "carts", res.Resource.Name)
require.Len(t, res.Resource.NetworkAddrs, 1)
require.Equal(t, "carts-db:27017", res.Resource.NetworkAddrs[0])
require.Len(t, res.Resource.Labels, 1)
require.Equal(t, "carts", res.Resource.Labels["name"])
}

func TestScanningDeploymentWithEnvs(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"k8s_guestbook", "frontend-deployment.yaml"})
require.Nil(t, err)
res, err := ScanK8sWorkloadObject("Deployment", resourceBuf)
require.Nil(t, err)
require.Equal(t, "frontend", res.Resource.Name)
require.Len(t, res.Resource.NetworkAddrs, 4)
require.Len(t, res.Resource.Labels, 2)
}

func TestScanningDeploymentWithConfigMapRef(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"acs-security-demos", "frontend", "webapp", "deployment.yaml"})
require.Nil(t, err)
res, err := ScanK8sWorkloadObject("Deployment", resourceBuf)
require.Nil(t, err)
require.Equal(t, "webapp", res.Resource.Name)
require.Len(t, res.Resource.ConfigMapRefs, 1)
require.Empty(t, res.Resource.NetworkAddrs) // extracting network addresses from configmaps happens later
require.Len(t, res.Resource.Labels, 1)
}

func TestScanningReplicaSet(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"k8s_guestbook", "redis-leader-deployment.yaml"})
require.Nil(t, err)
res, err := ScanK8sWorkloadObject("ReplicaSet", resourceBuf)
require.Nil(t, err)
require.Equal(t, "redis-leader", res.Resource.Name)
require.Len(t, res.Resource.NetworkAddrs, 0)
require.Len(t, res.Resource.Labels, 3)
}

func TestScanningConfigMap(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"qotd", "qotd_usecase.yaml"})
require.Nil(t, err)
res, err := ScanK8sConfigmapObject(resourceBuf)
require.Nil(t, err)
require.Equal(t, res.FullName, "qotd-load/qotd-usecase-library")
require.Len(t, res.Data, 5)
}

func TestScanningIngress(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"bookinfo", "bookinfo-ingress.yaml"})
require.Nil(t, err)
toExpose := common.ServicesToExpose{}
err = ScanIngressObject(resourceBuf, toExpose)
require.Nil(t, err)
require.Len(t, toExpose, 1)
}

func TestScanningRoute(t *testing.T) {
resourceBuf, err := loadResourceAsByteArray([]string{"acs-security-demos", "frontend", "webapp", "route.yaml"})
require.Nil(t, err)
toExpose := common.ServicesToExpose{}
err = ScanOCRouteObject(resourceBuf, toExpose)
require.Nil(t, err)
require.Len(t, toExpose, 1)
}

func loadResourceAsByteArray(resourceDirs []string) ([]byte, error) {
currentDir, _ := os.Getwd()
resourceRelPath := filepath.Join(resourceDirs...)
resourcePath := filepath.Join(currentDir, "..", "..", "tests", resourceRelPath)
return os.ReadFile(resourcePath)
}
8 changes: 4 additions & 4 deletions pkg/controller/resource_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,24 @@ func (rf *resourceFinder) parseK8sYaml(mfp, relMfp string) []FileProcessingError
func (rf *resourceFinder) parseResource(kind string, yamlDoc []byte, manifestFilePath string) error {
switch kind {
case service:
res, err := analyzer.ScanK8sServiceObject(kind, yamlDoc)
res, err := analyzer.ScanK8sServiceObject(yamlDoc)
if err != nil {
return err
}
res.Resource.FilePath = manifestFilePath
rf.services = append(rf.services, res)
case route:
err := analyzer.ScanOCRouteObject(kind, yamlDoc, rf.servicesToExpose)
err := analyzer.ScanOCRouteObject(yamlDoc, rf.servicesToExpose)
if err != nil {
return err
}
case ingress:
err := analyzer.ScanIngressObject(kind, yamlDoc, rf.servicesToExpose)
err := analyzer.ScanIngressObject(yamlDoc, rf.servicesToExpose)
if err != nil {
return err
}
case configmap:
res, err := analyzer.ScanK8sConfigmapObject(kind, yamlDoc)
res, err := analyzer.ScanK8sConfigmapObject(yamlDoc)
if err != nil {
return err
}
Expand Down

0 comments on commit 4d5493a

Please sign in to comment.