Skip to content

Conversation

@rjdenney
Copy link
Contributor

@rjdenney rjdenney commented Apr 4, 2023

Reason for Change:

This PR is to fix DualNIC log issue:
(1) If new CNS API(GetAllNetworkContainers) is failed to get ncConfigs, then it will try old CNS API(GetNetworkContainer)
(2) If old CNS API is also failed to get called, then it should return corresponding error instead of "Unsupported API" error

This PR addes three UT test cases:

  1. TestGetMultiTenancyCNIResultUnsupportedAPI() => This will test if new CNS API is not supported and old CNS API can handle to get ncConfig with "Unsupported API" error
  2. TestGetMultiTenancyCNIResultNotFound() => this test case includes two sub test cases:
       // 1. CNS supports new API and it does not have orchestratorContext info
       // 2. CNS does not support new API and it does not have orchestratorContext info

Issue Fixed:

Requirements:

Notes:

e.Code = types.UnsupportedAPI
e.Err = errors.New("Unsupported API")
return nil, e
} else {
Copy link
Member

Choose a reason for hiding this comment

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

remove else. You are already returning in if

}
}

func TestGetMultiTenancyCNIResultNotUnsupportedAPIError(t *testing.T) {
Copy link
Member

@ashvindeodhar ashvindeodhar Apr 4, 2023

Choose a reason for hiding this comment

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

// TestGetMultiTenancyCNIResultNotFound tests the scenario where CNS doesn't have the NC goal state for the provided orchestrator context
TestGetMultiTenancyCNIResultNotFound(

@tamilmani1989 tamilmani1989 added cni Related to CNI. fix Fixes something. labels Apr 4, 2023
@tamilmani1989
Copy link
Member

@paulyufan Please add description to your PR describing what's the issue and what you are fixing. That is must before we do any review

@rjdenney
Copy link
Contributor Author

rjdenney commented Apr 4, 2023

@tamilmani1989 Description has been updated.

@tamilmani1989 tamilmani1989 changed the title Fix dualNIC log issue and add UTs Fix incorrect error "UnsupportedAPI" returned by CNI Apr 4, 2023
tt.args.k8sPodName,
tt.args.k8sNamespace,
tt.args.ifName)
if (err != nil) != tt.wantErr {
Copy link
Member

Choose a reason for hiding this comment

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

the test case missing validating the actual error. we should validate if GetAllNetworkcontainers returning expected error message

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed

)

type MockCNSClient struct {
unsupportedAPIs map[cnsAPIName]bool
Copy link
Member

Choose a reason for hiding this comment

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

why do we need bool? just empty struct would do, no?

Copy link
Member

Choose a reason for hiding this comment

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

all we need to check if the key exists. We don;t really need a true / false value in the map


// set new CNS API is not supported
unsupportedAPIs := make(map[cnsAPIName]bool)
unsupportedAPIs["GetAllNetworkContainers"] = true
Copy link
Member

Choose a reason for hiding this comment

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

just need to add empty struct struct{}{}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
_, err := tt.args.plugin.multitenancyClient.GetAllNetworkContainers(
Copy link
Member

Choose a reason for hiding this comment

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

I think this is wrong -
if you call this func with GetAllNetworkContainers func in unsupported list, it will return ApiNotSupported error.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is fine. Logic is:
(1) Use new API GetAllNetworkContainers() => it goes to UnsupportedAPI
(2) Use old API GetNetworkContainer() => orchestratorContext is not same => errored.

return
}

if errors.Is(err, errNoOrchestratorContextFound) {
Copy link
Member

Choose a reason for hiding this comment

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

what if the err is not errNoOrchestratorContextFound, it will still pass the test, right? Which is what is happening for the first comment I made

Copy link
Member

@timraymond timraymond left a comment

Choose a reason for hiding this comment

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

I have some strongly suggested suggestions.

Comment on lines 87 to 96
if _, isUnsupported := c.unsupportedAPIs[GetAllNetworkContainers]; isUnsupported {
e := &client.CNSClientError{}
e.Code = types.UnsupportedAPI
e.Err = errUnsupportedAPI
return nil, e
}

if !reflect.DeepEqual(c.getAllNetworkContainersConfiguration.orchestratorContext, orchestratorContext) {
return nil, errNoOrchestratorContextFound
}
Copy link
Member

Choose a reason for hiding this comment

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

This stub cares too much about the data that it's handed. In the test we want to guarantee specific behavior from this stub so that specific reactive behavior can be tested. Behavior is the key, not the data. The problem with setting data and attempting to provide matching data to produce an expected stubbed behavior is that we basically end up with more untested code with bugs all of its own. It becomes difficult to modify the stub because there are other tests that depend on it.

Instead the tests should provide the implementation:

type MockCNSClient struct {
       GetAllNetworkContainersF(context.Context, []byte) ([]cns.GetNetworkContainersResponse, error)
}

func (m *MockCNSClient) GetAllNetworkContainers(ctx context.Context, ochestratorContext []byte) ([]cns.GetNetworkContainersResponse, error) {
       return m.GetAllNetworkContainersF(ctx, orchestratorContext)
}

That way when you create the mock client, you provide the exact implementation you expect the stubbed method call to do:

func TestSomething(t *testing.T) {
       cnsClient := &MockCNSClient{
              GetNetworkContainersF: func(_ context.Context, _ []byte) ([]cns.GetNetworkContainersResponse, error) {
                     return nil, errNoOrchestratorContextFound
              },
       }
}

Your tests become simpler because you don't have another source of complexity from the logic in the stub. I only mention this because there's enough change to the function of MockCNSClient to make this a worthwhile change.

Copy link
Member

Choose a reason for hiding this comment

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

thanks Tim. @paulyufan2 I agree with Tim, this is a better approach.

tt.args.k8sPodName,
tt.args.k8sNamespace,
tt.args.ifName)
if (err != nil) != tt.wantErr {
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit of a puzzling construction for future readers. Clear is better than clever. Prefer this:

if err != nil && !tt.WantErr {
       t.Fatal("unexpected error: err:", err)
}

if err == nil && tt.WantErr {
       t.Fatal("expected an error but none received")
}

The first can be read literally: "if the error is not present and I expected one..." the second "if no error was present but I did expect one...".

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed with this style

if (err != nil) != tt.wantErr {
// expect an error that GetAllNetworkContainers() fails to get all network containers
t.Errorf("GetAllNetworkContainers() error = %v, wantErr %v", err, tt.wantErr)
return
Copy link
Member

Choose a reason for hiding this comment

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

Use t.Fatal instead of t.Error if you want to stop the test from proceeding. It has the same effect through some special runtime tricks. return is always a smell in tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed, use t.Fatal() and t.Fatalf() instead

if tt.wantErr {
require.Error(err)
}
require.NoError(err)
Copy link
Member

Choose a reason for hiding this comment

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

Use either t.Fatal / t.Error or require in tests... seeing both can be confusing for future readers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed, use t.Fatal() and t.Fatalf() instead

return nil, e
}

if !reflect.DeepEqual(c.getAllNetworkContainersConfiguration.orchestratorContext, orchestratorContext) {
Copy link
Member

Choose a reason for hiding this comment

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

reflect.DeepEqual is rarely the correct func in a *_test.go file these days. The trouble is that it compares everything--unexported fields included. This is fine if you don't have any unexported fields, but that may only be true today, at the time you're committing the code. It makes the tests more brittle because they make it more difficult for compared structs to have internals that are invisible to outside observers. Use github.com/google/go-cmp/cmp and the cmp.Equal func instead (which is a drop-in replacement). The upside is that you can provide diffs when they aren't the same using cmp.Diff. That makes it much easier to hunt down broken things.

Copy link
Contributor

Choose a reason for hiding this comment

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

fixed, use cmp.Equal

@vipul-21 vipul-21 merged commit a024d7d into Azure:master Apr 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cni Related to CNI. fix Fixes something.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants