-
Notifications
You must be signed in to change notification settings - Fork 260
Fix incorrect error "UnsupportedAPI" returned by CNI #1889
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
Conversation
cni/network/multitenancy_test.go
Outdated
| e.Code = types.UnsupportedAPI | ||
| e.Err = errors.New("Unsupported API") | ||
| return nil, e | ||
| } else { |
There was a problem hiding this comment.
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
cni/network/multitenancy_test.go
Outdated
| } | ||
| } | ||
|
|
||
| func TestGetMultiTenancyCNIResultNotUnsupportedAPIError(t *testing.T) { |
There was a problem hiding this comment.
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(
|
@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 |
|
@tamilmani1989 Description has been updated. |
cni/network/multitenancy_test.go
Outdated
| tt.args.k8sPodName, | ||
| tt.args.k8sNamespace, | ||
| tt.args.ifName) | ||
| if (err != nil) != tt.wantErr { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
cni/network/multitenancy_test.go
Outdated
| ) | ||
|
|
||
| type MockCNSClient struct { | ||
| unsupportedAPIs map[cnsAPIName]bool |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
cni/network/multitenancy_test.go
Outdated
|
|
||
| // set new CNS API is not supported | ||
| unsupportedAPIs := make(map[cnsAPIName]bool) | ||
| unsupportedAPIs["GetAllNetworkContainers"] = true |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
cni/network/multitenancy_test.go
Outdated
| return | ||
| } | ||
|
|
||
| if errors.Is(err, errNoOrchestratorContextFound) { |
There was a problem hiding this comment.
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
timraymond
left a comment
There was a problem hiding this 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.
| 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 | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
cni/network/multitenancy_test.go
Outdated
| tt.args.k8sPodName, | ||
| tt.args.k8sNamespace, | ||
| tt.args.ifName) | ||
| if (err != nil) != tt.wantErr { |
There was a problem hiding this comment.
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...".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with this style
cni/network/multitenancy_test.go
Outdated
| 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
cni/network/multitenancy_test.go
Outdated
| return nil, e | ||
| } | ||
|
|
||
| if !reflect.DeepEqual(c.getAllNetworkContainersConfiguration.orchestratorContext, orchestratorContext) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed, use cmp.Equal
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. 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: