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

EndpointElem -> VPCResourceIntf #222

Merged
merged 29 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
78401ee
structs and main functionality of https://github.com/np-guard/vpc-net…
ShiriMoran Oct 12, 2023
9a4f947
Highlevel code and structs
ShiriMoran Oct 15, 2023
15970f4
subnetConnectivitySubtract code; still needs to fill inside functiona…
ShiriMoran Oct 16, 2023
9400063
Redefined made the connection set diff and added todos
ShiriMoran Oct 17, 2023
bf18d01
Minor reorgs
ShiriMoran Oct 17, 2023
625fa41
Export SubnetConnectivityMap and enable external creation for unit test
ShiriMoran Oct 17, 2023
d2fbe4e
Added grouping subnet unittesting as preliminery stage to writing uni…
ShiriMoran Oct 18, 2023
823503e
exporting functionality for unit test; SubnetConnectivitySubtract sho…
ShiriMoran Oct 18, 2023
769eb7a
semantic diff simple unit test
ShiriMoran Oct 18, 2023
5a652cc
improved semantic diff computation
ShiriMoran Oct 18, 2023
f735908
fixed a bug/typo, added ad-hoc printing functionality
ShiriMoran Oct 18, 2023
20b4e98
unit test written for current functionality
ShiriMoran Oct 18, 2023
ce31dda
lint comments
ShiriMoran Oct 19, 2023
6b0897b
Merge remote-tracking branch 'origin/main'
ShiriMoran Oct 23, 2023
cdeff24
Merge remote-tracking branch 'origin/main'
ShiriMoran Oct 31, 2023
b9220e6
Merge remote-tracking branch 'origin/main'
ShiriMoran Nov 6, 2023
027d643
Merge remote-tracking branch 'origin/main'
ShiriMoran Nov 7, 2023
a6c60ee
parsing arguments for diff analysis
ShiriMoran Nov 1, 2023
30184db
Added end-to-end support for semantic diff of subnets
ShiriMoran Nov 2, 2023
8e1cedb
fix typo
ShiriMoran Nov 2, 2023
44c11d3
headerOfAnalyzedVPC to reflect diff
ShiriMoran Nov 5, 2023
146f8d6
added end-to-end test flow
ShiriMoran Nov 5, 2023
3658742
diff test added
ShiriMoran Nov 5, 2023
d9b1414
lint comments
ShiriMoran Nov 5, 2023
8f43305
somehow cherrypick missed it
ShiriMoran Nov 7, 2023
dcfc10d
EndpointElem -> VPCResourceIntf
ShiriMoran Nov 7, 2023
f315fb0
Merge remote-tracking branch 'origin/main'
ShiriMoran Nov 8, 2023
b56b789
merge with main
ShiriMoran Nov 8, 2023
dd6b897
fixing an error introduced by merge
ShiriMoran Nov 8, 2023
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
89 changes: 74 additions & 15 deletions cmd/analyzer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import (
"github.com/np-guard/vpc-network-config-analyzer/pkg/vpcmodel"
)

const (
ParsingErr = "error parsing arguments:"
OutGenerationErr = "output generation error:"
InGenerationErr = "error generating cloud config from input vpc resources file:"
ErrorFormat = "%s %w"
)

func getOutputFormat(inArgs *InArgs) vpcmodel.OutFormat {
switch *inArgs.OutputFormat {
case TEXTFormat:
Expand All @@ -36,12 +43,14 @@ func analysisTypeToUseCase(inArgs *InArgs) vpcmodel.OutputUseCase {
return vpcmodel.SingleSubnet
case allSubnets:
return vpcmodel.AllSubnets
case allSubnetsDiff:
return vpcmodel.AllSubnetsDiff
}
return vpcmodel.AllEndpoints
}

func analysisPerVPCConfig(c *vpcmodel.VPCConfig, inArgs *InArgs, outFile string) (*vpcmodel.VPCAnalysisOutput, error) {
og, err := vpcmodel.NewOutputGenerator(c,
og, err := vpcmodel.NewOutputGenerator(c, nil,
*inArgs.Grouping,
analysisTypeToUseCase(inArgs),
*inArgs.OutputFormat == ARCHDRAWIOFormat)
Expand All @@ -57,12 +66,31 @@ func analysisPerVPCConfig(c *vpcmodel.VPCConfig, inArgs *InArgs, outFile string)
outFormat := getOutputFormat(inArgs)
output, err := og.Generate(outFormat, genOutFile)
if err != nil {
return nil, fmt.Errorf("output generation error: %w", err)
return nil, fmt.Errorf(ErrorFormat, OutGenerationErr, err)
}

return output, nil
}

func analysisDiffVPCConfig(c1, c2 *vpcmodel.VPCConfig, inArgs *InArgs, outFile string) (*vpcmodel.VPCAnalysisOutput, error) {
og, err := vpcmodel.NewOutputGenerator(c1, c2,
*inArgs.Grouping,
analysisTypeToUseCase(inArgs),
false)
if err != nil {
return nil, err
}

var analysisOut *vpcmodel.VPCAnalysisOutput
outFormat := getOutputFormat(inArgs)
analysisOut, err = og.Generate(outFormat, outFile)
if err != nil {
return nil, fmt.Errorf(ErrorFormat, OutGenerationErr, err)
}

return analysisOut, nil
}

// The actual main function
// Takes command-line flags and returns an error rather than exiting, so it can be more easily used in testing
func _main(cmdlineArgs []string) error {
Expand All @@ -71,7 +99,7 @@ func _main(cmdlineArgs []string) error {
return nil
}
if err != nil {
return fmt.Errorf("error parsing arguments: %w", err)
return fmt.Errorf(ErrorFormat, ParsingErr, err)
}

rc, err := ibmvpc.ParseResourcesFromFile(*inArgs.InputConfigFile)
Expand All @@ -81,30 +109,61 @@ func _main(cmdlineArgs []string) error {

vpcConfigs, err := ibmvpc.VPCConfigsFromResources(rc, *inArgs.VPC, *inArgs.Debug)
if err != nil {
return fmt.Errorf("error generating cloud config from input vpc resources file: %w", err)
return fmt.Errorf(ErrorFormat, InGenerationErr, err)
}

outFile := ""
if inArgs.OutputFile != nil {
outFile = *inArgs.OutputFile
}

outputPerVPC := make([]*vpcmodel.VPCAnalysisOutput, len(vpcConfigs))
i := 0
for _, vpcConfig := range vpcConfigs {
vpcAnalysisOutput, err2 := analysisPerVPCConfig(vpcConfig, inArgs, outFile)
diffAnalysis := *inArgs.AnalysisType == allEndpointsDiff || *inArgs.AnalysisType == allSubnetsDiff
if !diffAnalysis {
outputPerVPC := make([]*vpcmodel.VPCAnalysisOutput, len(vpcConfigs))
i := 0
for _, vpcConfig := range vpcConfigs {
vpcAnalysisOutput, err2 := analysisPerVPCConfig(vpcConfig, inArgs, outFile)
if err2 != nil {
return err2
}
outputPerVPC[i] = vpcAnalysisOutput
i++
}

var out string
out, err = vpcmodel.AggregateVPCsOutput(outputPerVPC, getOutputFormat(inArgs), outFile)
if err != nil {
return err
}
fmt.Println(out)
} else {
// Diff analysis
// ToDo SM: for diff analysis assume 2 configs only, the 2nd given through vpc-config-second
var rc2ndForDiff *ibmvpc.ResourcesContainer
rc2ndForDiff, err = ibmvpc.ParseResourcesFromFile(*inArgs.InputSecondConfigFile)
if err != nil {
return fmt.Errorf(ErrorFormat, ParsingErr, err)
}
vpc2ndConfigs, err := ibmvpc.VPCConfigsFromResources(rc2ndForDiff, *inArgs.VPC, *inArgs.Debug)
if err != nil {
return fmt.Errorf(ErrorFormat, InGenerationErr, err)
}
// For diff analysis each vpcConfigs have a single element
c1 := getFirstCfg(vpcConfigs)
c2 := getFirstCfg(vpc2ndConfigs)
analysisOutput, err2 := analysisDiffVPCConfig(c1, c2, inArgs, outFile)
if err2 != nil {
return err2
}
outputPerVPC[i] = vpcAnalysisOutput
i++
fmt.Println(analysisOutput.Output)
}
return nil
}

out, err := vpcmodel.AggregateVPCsOutput(outputPerVPC, getOutputFormat(inArgs), outFile)
if err != nil {
return err
func getFirstCfg(vpcConfigs map[string]*vpcmodel.VPCConfig) *vpcmodel.VPCConfig {
for _, vpcConfig := range vpcConfigs {
return vpcConfig
}
fmt.Println(out)

return nil
}

Expand Down
81 changes: 55 additions & 26 deletions cmd/analyzer/parse_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (

// InArgs contains the input arguments for the analyzer
type InArgs struct {
InputConfigFile *string
OutputFile *string
OutputFormat *string
AnalysisType *string
Grouping *bool
VPC *string
Debug *bool
InputConfigFile *string
InputSecondConfigFile *string
OutputFile *string
OutputFormat *string
AnalysisType *string
Grouping *bool
VPC *string
Debug *bool
}

const (
Expand All @@ -27,9 +28,11 @@ const (
DEBUGFormat = "debug"

// connectivity analysis types supported
allEndpoints = "all_endpoints" // vsi to vsi connectivity analysis
allSubnets = "all_subnets" // subnet to subnet connectivity analysis
singleSubnet = "single_subnet" // single subnet connectivity analysis
allEndpoints = "all_endpoints" // vsi to vsi connectivity analysis
allSubnets = "all_subnets" // subnet to subnet connectivity analysis
singleSubnet = "single_subnet" // single subnet connectivity analysis
allEndpointsDiff = "diff_all_endpoints" // semantic diff of allEndpoints analysis between two configurations
allSubnetsDiff = "diff_all_subnets" // semantic diff of allSubnets analysis between two configurations
)

var supportedOutputFormats = map[string]bool{
Expand All @@ -41,9 +44,11 @@ var supportedOutputFormats = map[string]bool{
DEBUGFormat: true,
}
var supportedAnalysisTypes = map[string]bool{
allEndpoints: true,
allSubnets: true,
singleSubnet: true,
allEndpoints: true,
allSubnets: true,
singleSubnet: true,
allSubnetsDiff: true,
allEndpointsDiff: false,
}

func getSupportedValuesString(supportedValues map[string]bool) string {
Expand All @@ -60,6 +65,7 @@ func ParseInArgs(cmdlineArgs []string) (*InArgs, error) {
args := InArgs{}
flagset := flag.NewFlagSet("vpc-network-config-analyzer", flag.ContinueOnError)
args.InputConfigFile = flagset.String("vpc-config", "", "file path to input config")
args.InputSecondConfigFile = flagset.String("vpc-config-second", "", "file path to second input config for semantic diff")
args.OutputFile = flagset.String("output-file", "", "file path to store results")
args.OutputFormat = flagset.String("format", TEXTFormat, "output format; must be one of "+getSupportedValuesString(supportedOutputFormats))
args.AnalysisType = flagset.String("analysis-type", allEndpoints,
Expand All @@ -72,32 +78,55 @@ func ParseInArgs(cmdlineArgs []string) (*InArgs, error) {
if err != nil {
return nil, err
}
err = errorInErgs(&args, flagset)
if err != nil {
return nil, err
}
err = notSupportedYetArgs(&args)
if err != nil {
return nil, err
}

return &args, nil
}

func errorInErgs(args *InArgs, flagset *flag.FlagSet) error {
if args.InputConfigFile == nil || *args.InputConfigFile == "" {
flagset.PrintDefaults()
return nil, fmt.Errorf("missing parameter: vpc-config")
return fmt.Errorf("missing parameter: vpc-config")
}

if !supportedOutputFormats[*args.OutputFormat] {
if !supportedAnalysisTypes[*args.AnalysisType] {
flagset.PrintDefaults()
return nil, fmt.Errorf("wrong output format %s; must be one of %s", *args.OutputFormat, getSupportedValuesString(supportedOutputFormats))
return fmt.Errorf("wrong analysis type %s; must be one of: %s", *args.AnalysisType, getSupportedValuesString(supportedAnalysisTypes))
}

if !supportedAnalysisTypes[*args.AnalysisType] {
if !supportedOutputFormats[*args.OutputFormat] {
flagset.PrintDefaults()
return nil, fmt.Errorf("wrong analysis type %s; must be one of: %s", *args.AnalysisType, getSupportedValuesString(supportedAnalysisTypes))
return fmt.Errorf("wrong output format %s; must be one of %s", *args.OutputFormat, getSupportedValuesString(supportedOutputFormats))
}
diffAnalysis := *args.AnalysisType == allEndpointsDiff || *args.AnalysisType == allSubnetsDiff
fileForDiffSpecified := args.InputSecondConfigFile != nil && *args.InputSecondConfigFile != ""
if fileForDiffSpecified && !diffAnalysis {
return fmt.Errorf("wrong analysis type %s for 2nd file (%v) specified for diff",
*args.AnalysisType, *args.InputSecondConfigFile)
}
if !fileForDiffSpecified && diffAnalysis {
return fmt.Errorf("missing parameter vpc-config-second for diff analysis %s", *args.AnalysisType)
}
return nil
}

func notSupportedYetArgs(args *InArgs) error {
if *args.AnalysisType != allEndpoints && *args.OutputFormat != TEXTFormat && *args.OutputFormat != JSONFormat {
return nil, fmt.Errorf("currently only txt/json output format supported with %s analysis type", *args.AnalysisType)
return fmt.Errorf("currently only txt/json output format supported with %s analysis type", *args.AnalysisType)
}

if *args.AnalysisType == singleSubnet && *args.Grouping {
return nil, fmt.Errorf("currently singleSubnet analysis type does not support grouping")
return fmt.Errorf("currently singleSubnet analysis type does not support grouping")
}

if *args.OutputFormat == JSONFormat && *args.Grouping {
return nil, fmt.Errorf("json output format is not supported with grouping")
return fmt.Errorf("json output format is not supported with grouping")
}
return &args, nil
if *args.AnalysisType == allSubnetsDiff && *args.OutputFormat != TEXTFormat {
return fmt.Errorf("currently only txt output format supported with diff_all_subnets")
}
return nil
}
50 changes: 41 additions & 9 deletions pkg/ibmvpc/analysis_output_test.go
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this file changed on this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

problem that occurred during merge. fixed

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
type vpcGeneralTest struct {
name string // test name
inputConfig string // name (relative path) of input config file (json)
inputConfig2nd string // 2nd input file for diff
expectedOutput map[vpcmodel.OutputUseCase]string // expected output file path
actualOutput map[vpcmodel.OutputUseCase]string // actual output file path
useCases []vpcmodel.OutputUseCase // the list of output use cases to test
Expand All @@ -50,6 +51,7 @@ const (
suffixOutFileDebugSubnet = "_analysisPerSubnetSeparately"
suffixOutFileSubnetsLevel = "subnetsBased_withPGW"
suffixOutFileSubnetsLevelNoPGW = "subnetsBased_withoutPGW"
suffixOutFileDiffSubnets = "subnetsDiff"
txtOutSuffix = ".txt"
debugOutSuffix = "_debug.txt"
mdOutSuffix = ".md"
Expand Down Expand Up @@ -93,6 +95,8 @@ func getTestFileName(testName string,
res = baseName + suffixOutFileSubnetsLevel
case vpcmodel.AllSubnetsNoPGW:
res = baseName + suffixOutFileSubnetsLevelNoPGW
case vpcmodel.AllSubnetsDiff:
res = baseName + suffixOutFileDiffSubnets
}
switch format {
case vpcmodel.Text:
Expand Down Expand Up @@ -120,9 +124,9 @@ func getTestFileName(testName string,
// files names (actual and expected), per use case
func (tt *vpcGeneralTest) initTest() {
tt.inputConfig = inputFilePrefix + tt.name + ".json"
tt.inputConfig2nd = inputFilePrefix + tt.name + "_2nd.json"
tt.expectedOutput = map[vpcmodel.OutputUseCase]string{}
tt.actualOutput = map[vpcmodel.OutputUseCase]string{}

// init field of expected errs
if tt.errPerUseCase == nil {
tt.errPerUseCase = map[vpcmodel.OutputUseCase]error{}
Expand Down Expand Up @@ -326,6 +330,11 @@ var tests = []*vpcGeneralTest{
useCases: []vpcmodel.OutputUseCase{vpcmodel.AllSubnets},
format: vpcmodel.Text,
},
{
name: "acl_testing5",
useCases: []vpcmodel.OutputUseCase{vpcmodel.AllSubnetsDiff},
format: vpcmodel.Text,
},
}

var formatsAvoidComparison = map[vpcmodel.OutFormat]bool{vpcmodel.ARCHDRAWIO: true, vpcmodel.DRAWIO: true}
Expand Down Expand Up @@ -387,11 +396,23 @@ func (tt *vpcGeneralTest) runTest(t *testing.T) {
tt.initTest()

// get vpcConfigs obj from parsing + analyzing input config file
vpcConfigs := getVPCConfigs(t, tt)
vpcConfigs := getVPCConfigs(t, tt, true)
var vpcConfigs2nd map[string]*vpcmodel.VPCConfig
diffUseCase := false
for _, useCase := range tt.useCases {
if useCase == vpcmodel.AllSubnetsDiff {
diffUseCase = true
}
}
if diffUseCase {
vpcConfigs2nd = getVPCConfigs(t, tt, false)
} else { // inputConfig2nd should be ignored if not diffUseCase
tt.inputConfig2nd = ""
}

// generate actual output for all use cases specified for this test
for _, uc := range tt.useCases {
err := runTestPerUseCase(t, tt, vpcConfigs, uc, tt.mode)
err := runTestPerUseCase(t, tt, vpcConfigs, vpcConfigs2nd, uc, tt.mode)
require.Equal(t, tt.errPerUseCase[uc], err, "comparing actual err to expected err")
}
for uc, outFile := range tt.actualOutput {
Expand All @@ -400,8 +421,14 @@ func (tt *vpcGeneralTest) runTest(t *testing.T) {
}

// getVPCConfigs returns map[string]*vpcmodel.VPCConfig obj for the input test (config json file)
func getVPCConfigs(t *testing.T, tt *vpcGeneralTest) map[string]*vpcmodel.VPCConfig {
inputConfigFile := filepath.Join(getTestsDir(), tt.inputConfig)
func getVPCConfigs(t *testing.T, tt *vpcGeneralTest, firstCfg bool) map[string]*vpcmodel.VPCConfig {
var inputConfig string
if firstCfg {
inputConfig = tt.inputConfig
} else {
inputConfig = tt.inputConfig2nd
}
inputConfigFile := filepath.Join(getTestsDir(), inputConfig)
inputConfigContent, err := os.ReadFile(inputConfigFile)
if err != nil {
t.Fatalf("err: %s", err)
Expand Down Expand Up @@ -461,18 +488,23 @@ func initTestFileNames(tt *vpcGeneralTest,
// runTestPerUseCase runs the connectivity analysis for the required use case and compares/generates the output
func runTestPerUseCase(t *testing.T,
tt *vpcGeneralTest,
c map[string]*vpcmodel.VPCConfig,
c1, c2 map[string]*vpcmodel.VPCConfig,
uc vpcmodel.OutputUseCase,
mode testMode) error {
numConfigs := len(c)
numConfigs := len(c1)
allVPCsOutput := make([]*vpcmodel.VPCAnalysisOutput, numConfigs)
i := 0
for _, vpcConfig := range c {
var vpcConfig2nd *vpcmodel.VPCConfig
// note that for diff analysis mode a single vpcConfig in c1 is provided; c2 is assumed to have a single cfg
for _, vpcConfig := range c2 {
vpcConfig2nd = vpcConfig
}
for _, vpcConfig := range c1 {
if err := initTestFileNames(tt, uc, numConfigs, vpcConfig.VPC.Name(), false); err != nil {
return err
}

og, err := vpcmodel.NewOutputGenerator(vpcConfig, tt.grouping, uc, tt.format == vpcmodel.ARCHDRAWIO)
og, err := vpcmodel.NewOutputGenerator(vpcConfig, vpcConfig2nd, tt.grouping, uc, tt.format == vpcmodel.ARCHDRAWIO)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/ibmvpc/examples/acl_testing5subnetsBased_withPGW.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Analysis for VPC test-vpc-ky
Analysis for VPC test-vpc-ky1
combined connections between subnets:
sub1-1-ky => Public Internet 8.8.8.8/32 : protocol: UDP dst-ports: 53
sub1-1-ky => sub1-2-ky : protocol: TCP
Expand Down
Loading
Loading