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

Add E2E tests #32

Merged
merged 3 commits into from
Jun 27, 2018
Merged

Add E2E tests #32

merged 3 commits into from
Jun 27, 2018

Conversation

xmudrii
Copy link
Owner

@xmudrii xmudrii commented Jun 26, 2018

This PR adds E2E tests for the EtcdProxyController.

The E2E tests assumes you have a working Kubernetes cluster and EtcdProxy Controller deployed. The hack/run-e2e.sh script, which is used by CI, deploys the controller from the deployment manifest located in the artifacts directory.

By default, tests use the kubeconfig located in $HOME/.kube/config. The kubeconfig path can be changed by modifying the kubeconfigPath variable in the test/e2e/e2e_test.go file.

The tests are located in the test/e2e/e2e_test.go file. Currently, we have one test function which tests:

  • Is EtcdStorage creation successful,
  • Are EtcdStorage conditions set as intended,
  • Is ReplicaSet with the expected name deployed,
  • Is expected number of pods available (running),
  • Is Service with the expected name created.

We're testing two cases for now:

  • EtcdStorage is valid,
  • EtcdStorage name is too long.

I'm also working on testing are all resources going to be deleted once the EtcdStorage object is deleted.

@xmudrii xmudrii added the do-not-merge/wip Indicates Work-In-Progress Pull Requests. label Jun 26, 2018
Copy link
Owner Author

@xmudrii xmudrii left a comment

Choose a reason for hiding this comment

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

I've left some questions.

Makefile Outdated
verify-ci: install-tools ## Run code checks
PKGS="${GOFILES}" GOFMT="gofmt" ./hack/verify-ci.sh
.PHONY: test-e2e
test-e2e: ## Run E2E tests. Requires working Kubernetes cluster and a Kubeconfig file.
Copy link
Owner Author

Choose a reason for hiding this comment

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

This should be updated to use the run-e2e.sh script.

A follow-up question: do we need to leave a note that this is potentially destructive operation and to be sure you're running it on the correct cluster?

Copy link
Collaborator

Choose a reason for hiding this comment

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

A follow-up question: do we need to leave a note that this is potentially destructive operation and to be sure you're running it on the correct cluster?

note that your tests may be destructive. We can assume these people know what they're doing and get what they deserve.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Added a note and fixed the command.

)

// kubeconfigPath is path to the kubernetes configuration file.
var kubeconfigPath = filepath.Join(os.Getenv("HOME"), "/.kube/config")
Copy link
Owner Author

Choose a reason for hiding this comment

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

Is there a better way for providing kubeconfigPath? Maybe with an environment variable, or this is good enough?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a better way for providing kubeconfigPath? Maybe with an environment variable, or this is good enough?

We respect KUBECONFIG in most (all?) kube binaries. I'd use that.

Copy link
Owner Author

Choose a reason for hiding this comment

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

This is a great idea, updated the PR to use it.

var kubeconfigPath = filepath.Join(os.Getenv("HOME"), "/.kube/config")

// newKubeClient create new Kubernetes client instance based on kubeconfig file.
func newKubeClient(kubeconfigPath string) (*kubernetes.Clientset, error) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Is it okay to leave those functions in this file, or should I create a new file such as util.go?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it okay to leave those functions in this file, or should I create a new file such as util.go?

don't create a "util" anything until absolutely necessary. Let's say 1000 lines. They generally become a bucket of stuff.

return clientset.NewForConfig(config)
}

func replicasetName(etcdstorageName string) string {
Copy link
Owner Author

Choose a reason for hiding this comment

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

My idea was to have a helper functions for calculating names, instead of hardcoding them in test cases, to make potential changes easier.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My idea was to have a helper functions for calculating names, instead of hardcoding them in test cases, to make potential changes easier.

I generally question the utility of one liners. Do you think it's likely we'll change our minds? How many places would we really have to update it? Should such a change be hard since we're changing external behavior?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Do you think it's likely we'll change our minds?

I don't think we'll ever change those. Even if we do in some case, that would be just a few lines to update. Let's get rid of those functions.

return fmt.Sprintf("etcd-%s", etcdstorageName)
}

func TestDeployEtcdStorage(t *testing.T) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Is it okay to have all these checks in this function, or I should split them to multiple tests? In this function we're testing is the EtcdStorage instance deployed, and are all relevant resources deployed.

TODO: add godoc comment

Copy link
Owner Author

Choose a reason for hiding this comment

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

Added godoc comment.

}

// Only continue if etcdStorage is expected to be valid.
if tc.etcdStorageValid {
Copy link
Owner Author

Choose a reason for hiding this comment

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

I have a problem here.

We have defined that EtcdStorages name length should be less than 59 characters (64 which is max for Services, -5 for etcd- prefix). I added a test case for that, but, this is going to fail on Create.

But that failure is expected, so I was not sure how to stop there. I added etcdStorageValid bool, and if it is false, error on Create is going to be ignored, and we're not going to continue.

Is there a better way to mark test as passed, than this one? There is 'FailNow(), which fails, but I don't want fail.
There is also SkipNow(), but it marks test as skipped, and I was not sure do we want that.

// Only continue if etcdStorage is expected to be valid.
if tc.etcdStorageValid {
// It takes some time for the ReplicaSet and Service to be created, so we need to poll.
err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

TODO: check for error.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Fixed.

t.Fatalf("expected replicaset '%s', but got: %v", tc.expectedReplicaSetName, err)
}
// Wait for pods to become ready.
err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

TODO: check for error.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Fixed.

}

// Check is ReplicaSet deployed.
rs, err := client.AppsV1().ReplicaSets("kube-apiserver-storage").Get(tc.expectedReplicaSetName, metav1.GetOptions{})
Copy link
Owner Author

Choose a reason for hiding this comment

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

This is not needed, we have it in wait.Poll.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Fixed.

.travis.yml Outdated
script: make test-e2e
before_script:
# Download kubectl, which is a requirement for using minikube and running e2e tests.
- curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${KUBERNETES_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
Copy link
Collaborator

Choose a reason for hiding this comment

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

@soltysh @liggitt next release I'm solving my oc problems like this! :)

Copy link

Choose a reason for hiding this comment

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

I object 😝

.travis.yml Outdated
# Update the kubeconfig to use the minikube cluster.
- minikube update-context
# Wait for Kubernetes to be up and ready.
- JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
Copy link
Collaborator

Choose a reason for hiding this comment

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

In addition, I'd suggest waiting for the server's /healthz to report true.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Added a TODO note. I'll try to get it for this PR.

return kubernetes.NewForConfig(config)
}

func newEtcdProxyClinet(kubeconfigPath string) (*clientset.Clientset, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo

Copy link
Owner Author

Choose a reason for hiding this comment

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

🙈


// newEtcdProxyClient creates new client for etcdstorage resources.
func newEtcdProxyClient() (*clientset.Clientset, error) {
config, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would have one helper to create the config and then do the NewForConfig call inline. I also tolerate NewForConfigOrDie in tests.

etcdStorageValid: true,
},
{
name: "test etcdstorage creation - name too long",
Copy link
Collaborator

Choose a reason for hiding this comment

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

this should be a unit test

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Create an EtcdStorage instance.
es, err := etcdproxyClient.EtcdV1alpha1().EtcdStorages().Create(tc.etcdStorage)
Copy link
Collaborator

Choose a reason for hiding this comment

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

missing deletion/cleanup

Copy link
Owner Author

Choose a reason for hiding this comment

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

Added.

@deads2k
Copy link
Collaborator

deads2k commented Jun 26, 2018

This is fine, but in general I expect my e2e tests to tell a story. In kube, they alias kubectl and use an exec helper to instantiate manifests and check status on that.

The ideal e2e test would be creating your sample manifests. If you want to simply create a bash script you call to do that for now, that would be fine, but I think it is important to do.

@xmudrii xmudrii force-pushed the e2e-tests branch 2 times, most recently from 467d4e3 to d80f700 Compare June 26, 2018 15:46
@xmudrii
Copy link
Owner Author

xmudrii commented Jun 27, 2018

@deads2k I've addressed comments from yesterday and added an E2E story for the sample-apiserver. Compared to EtcdStorage story, which is using a Bash script + Go, the sample-apiserver story is only using the bash script.

The tests for sample-apiserver are checking is the pod ready. This is probably not enough, but I don't have idea what else could I check. If you have some ideas, let me know. :)

Also, I've disabled E2E tests in Travis as they're failing. The tests are passing locally, so for now, let's leave it off if you agree.

@xmudrii xmudrii changed the title [WIP] Add E2E tests Add E2E tests Jun 27, 2018
@xmudrii xmudrii added ready-for-review Indicates that a Pull Request is ready for review. and removed do-not-merge/wip Indicates Work-In-Progress Pull Requests. labels Jun 27, 2018
@xmudrii xmudrii added this to the Milestone 2 milestone Jun 27, 2018
READY_REPLICAS=$(kubectl get rs apiserver -o jsonpath="{.status.readyReplicas}")
done

echo '* Starting sample-apiserver e2e tests.'
Copy link
Collaborator

Choose a reason for hiding this comment

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

followup issue to actually create a resource.

// Initialize clients.
cfg, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
if err != nil {
t.Fatalf("unable to create kubernetes config from provided kubeconfig file: %v", err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I generally just use t.Fatal(err). You get a line number, so you can track it back easily enough.

@deads2k
Copy link
Collaborator

deads2k commented Jun 27, 2018

Also, I've disabled E2E tests in Travis as they're failing. The tests are passing locally, so for now, let's leave it off if you agree.

Yeah, that's fine. Don't forget to run your e2e and tell us in the description whether it worked or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-for-review Indicates that a Pull Request is ready for review.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants