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

Remove KubeConfig Dependency for Store Tests #2789

Merged
merged 1 commit into from
Jul 17, 2018

Conversation

diazjf
Copy link

@diazjf diazjf commented Jul 16, 2018

Removes the KubeConfig Dependency for the Store Test by using the FakeClient Instead. Unit Tests should not rely on a real KubeConfig.

See #2788

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jul 16, 2018
@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch from a125e2a to b5f32b4 Compare July 16, 2018 16:33
@aledbf
Copy link
Member

aledbf commented Jul 16, 2018

@diazjf to be sure this is ok, please also remove https://github.com/kubernetes/ingress-nginx/blob/master/.travis.yml#L38 (these tests are the only reason why we start a cluster in tests)

@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch from b5f32b4 to 786b80b Compare July 16, 2018 16:54
@codecov-io
Copy link

codecov-io commented Jul 16, 2018

Codecov Report

Merging #2789 into master will decrease coverage by 0.25%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2789      +/-   ##
==========================================
- Coverage   47.58%   47.33%   -0.26%     
==========================================
  Files          75       75              
  Lines        5413     5413              
==========================================
- Hits         2576     2562      -14     
- Misses       2509     2527      +18     
+ Partials      328      324       -4
Impacted Files Coverage Δ
cmd/nginx/main.go 6.57% <ø> (-13.82%) ⬇️
internal/watch/file_watcher.go 80.76% <0%> (-3.85%) ⬇️
internal/ingress/controller/store/store.go 62.68% <0%> (+2.33%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 12f7966...c783c08. Read the comment docs.

@diazjf
Copy link
Author

diazjf commented Jul 16, 2018

@aledbf main_test.go still fails since I haven't edited that file. store_test.go should pass. I will update main_test.go in this PR and let you know.

@aledbf
Copy link
Member

aledbf commented Jul 16, 2018

@diazjf thank you for removing this dependency

@antoineco
Copy link
Contributor

Excellent 👏 , I also wanted to tackle this, the dependency on a real API was annoying.

@@ -605,37 +619,89 @@ func TestStore(t *testing.T) {
// check invalid secret (missing ca)
}

func createNamespace(clientSet *kubernetes.Clientset, t *testing.T) string {
func createNamespace(clientSet *fake.Clientset, t *testing.T) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is still using a specific type, you can replace any Clientset by kubernetes.Interface to make it portable.

Copy link
Author

Choose a reason for hiding this comment

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

I don't think we can cast fake.ClientSet to kubernetes.Clientset.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not ClientSet, Interface. All REST clients implement it, including fake.

Copy link
Author

Choose a reason for hiding this comment

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

Can you provide me an example of how it should look. Having some issues trying to properly type-cast.

Copy link
Contributor

@antoineco antoineco Jul 16, 2018

Choose a reason for hiding this comment

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

All you have to do is replace clientSet *fake.Clientset with clientSet kubernetes.Interface, where kubernetes is k8s.io/client-go/kubernetes.
There is nothing to cast since you're just calling methods provided by the interface.

Copy link
Author

@diazjf diazjf Jul 16, 2018

Choose a reason for hiding this comment

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

Awesome Thanks, I see it now:

fakeclient:

clientset "k8s.io/client-go/kubernetes"
clientset.Interface = &Clientset{}

# &Clientset{} is:
# type Clientset struct {
#	testing.Fake
#	discovery *fakediscovery.FakeDiscovery
#}

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason why it works is not the contents of the struct, just the methods the struct implements (in other words, Admissionregistration() ... StorageV1beta1())

"sync"
"sync/atomic"
"testing"
"time"

"github.com/eapache/channels"
"k8s.io/api/extensions/v1beta1"
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice catch 👍

Copy link
Author

Choose a reason for hiding this comment

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

Thanks!

if err != nil {
if k8sErrors.IsNotFound(err) {
return clientSet.ExtensionsV1beta1().Ingresses(ingress.Namespace).Create(ingress)
t.Logf("ingress %v not found, creating", ingress)
return clientSet.Extensions().Ingresses(ingress.Namespace).Create(ingress)
}
return nil, err
Copy link
Contributor

@antoineco antoineco Jul 16, 2018

Choose a reason for hiding this comment

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

You should avoid returning errors in tests and simply call t.Fatal in case an error occurs, like you did above. Call t.Helper() at the beginning of the function in that case.

Copy link
Author

Choose a reason for hiding this comment

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

Done

@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch from b19883f to f890769 Compare July 16, 2018 18:36
func ensureIngress(ingress *extensions.Ingress, clientSet *kubernetes.Clientset) (*extensions.Ingress, error) {
s, err := clientSet.ExtensionsV1beta1().Ingresses(ingress.Namespace).Update(ingress)
func createConfigMap(clientSet *fake.Clientset, ns string, t *testing.T) string {
t.Log("creating temporal config map")
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a helper function, adding t.Helper() here would allow logs to report line numbers accurately.

return ing
}

func deleteIngress(ingress *extensions.Ingress, clientSet *fake.Clientset, t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a helper function, adding t.Helper() here would allow logs to report line numbers accurately.

return cm.Name
}

func deleteConfigMap(cm, ns string, clientSet *fake.Clientset, t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a helper function, adding t.Helper() here would allow logs to report line numbers accurately.

t.Logf("temporal configmap %v deleted", cm)
}

func ensureIngress(ingress *extensions.Ingress, clientSet *fake.Clientset, t *testing.T) *extensions.Ingress {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a helper function, adding t.Helper() here would allow logs to report line numbers accurately.

ObjectMeta: metav1.ObjectMeta{
Name: "dummy",
Namespace: ns,
SelfLink: fmt.Sprintf("/apis/extensions/v1beta1/namespaces/%s/ingresses/dummy", ns),
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this field generated by the API server, and not the user?

Copy link
Author

Choose a reason for hiding this comment

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

I don't think the fakeClient mocks generating this field, when it is not provided in the ingress the following is obtained:

Could not construct reference to: '<ingress-resource>' due to: 'selfLink was empty, can't make reference'

if err != nil {
t.Errorf("unexpected error creating ingress: %v", err)
t.Errorf("unexpected error waiting for secret: %v", err)
Copy link
Contributor

@antoineco antoineco Jul 16, 2018

Choose a reason for hiding this comment

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

(my favorite review comment, sorry for repeating myself) Log messages should start with a capital letter to differentiate them from error messages returned by functions.
Besides errors are never expected, so removing the word "unexpected" from all these log messages makes them shorter and to the point. But that's obviously a nit pick, these messages are not user facing so feel free to skip that comment.

Copy link
Author

Choose a reason for hiding this comment

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

Done

}
time.Sleep(1 * time.Second)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need that sleep with the fake client?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I noticed that if we don't wait a second, the event wont get generated by the time we need it to.

@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch from f890769 to 8fd8984 Compare July 16, 2018 20:23
@diazjf
Copy link
Author

diazjf commented Jul 16, 2018

@aledbf @antoineco So I can get store_test.go working without KubeClient, but main_test.go will require some more effort, are you guys opposed to just merging this once all the issues have been resolved, and I can submit another PR in the future for main_test.go?

By main_test.go I mean https://github.com/kubernetes/ingress-nginx/blob/master/cmd/nginx/main_test.go I'm thinking it looks more like a functional test, also I'm wondering where this is used.

@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch 3 times, most recently from cb5e1bc to 753cdc7 Compare July 16, 2018 21:06
@diazjf
Copy link
Author

diazjf commented Jul 16, 2018

So tests are passing!! Give me 👍 and I'll revert .travis and main_test.go back to normal.

@antoineco @aledbf

@aledbf aledbf added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 17, 2018
@antoineco
Copy link
Contributor

Awesome work, thanks @diazjf! What issue are you facing exactly with main_test.go? Maybe we can rework the tests because not everything makes sense to test (eg. NewClientSet)

@aledbf aledbf removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 17, 2018
@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch from 753cdc7 to 4e71431 Compare July 17, 2018 15:56
@diazjf
Copy link
Author

diazjf commented Jul 17, 2018

@antoineco thanks for all the help. It seems like createApiserverClient relies on a real Kubernetes Cluster and we may not be able to get by with the fakeClient. I have reworked the tests not to use the ApiServerClient. Let me know what you think.

@aledbf

if err == nil {
t.Fatal("Expected an error creating REST client without an API server URL or kubeconfig file.")
t.Fatal("expected an error creating REST client without an API server URL or kubeconfig file.")
Copy link
Contributor

@antoineco antoineco Jul 17, 2018

Choose a reason for hiding this comment

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

Capital letter was fine (log message, cf previous comment).

Copy link
Author

Choose a reason for hiding this comment

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

Will change back.

}

_, err = createApiserverClient("", "")
_, err := createApiserverClient("", "")
Copy link
Contributor

Choose a reason for hiding this comment

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

This calls kubernetes.NewForConfig(), which will always fail without API. Not sure if it's really interesting to test createApiserverClient() at all, unless we make it testable (eg. create a fake client if called by tests).

Copy link
Author

Choose a reason for hiding this comment

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

I think it will be a bigger effort to make it more testable, but maybe we can set that for a future release.

Copy link
Author

Choose a reason for hiding this comment

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

I can look into this in the future if its ok.


ns := "test"

cm := createConfigMap(clientSet, ns, t)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure to understand why this is here?

Copy link
Author

Choose a reason for hiding this comment

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

Since controller.NewNGINXController(conf, nil, fs) make a call to the store. The store sets a configuration and requires a configmap. If it is not created then there will be a segmentation fault.

Removes the KubeConfig Dependency for the Store Test by using the
FakeClient Instead. Unit Tests should not rely on a real KubeConfig.

Fixes kubernetes#2789
@diazjf diazjf force-pushed the mock-kube-in-unit-tests branch from 4e71431 to c783c08 Compare July 17, 2018 16:13
@antoineco
Copy link
Contributor

antoineco commented Jul 17, 2018

once all the issues have been resolved, and I can submit another PR in the future for main_test.go?

I'm fine refactoring the cmd/nginx tests in a separate PR. @aledbf?

edit: sorry, didn't see the "hold" label had been removed.

@antoineco
Copy link
Contributor

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jul 17, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: antoineco, diazjf

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 17, 2018
@k8s-ci-robot k8s-ci-robot merged commit 96c289b into kubernetes:master Jul 17, 2018
@aledbf
Copy link
Member

aledbf commented Jul 17, 2018

I'm fine refactoring the cmd/nginx tests in a separate PR. @aledbf?

👍

@aledbf
Copy link
Member

aledbf commented Jul 17, 2018

edit: sorry, didn't see the "hold" label had been removed.

I removed the label after the 0.17.1 release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants