-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Helm integration test framework #237
Conversation
// RunHelmCommandAndGetOutputE runs helm with the given arguments and options and returns stdout/stderr. | ||
func RunHelmCommandAndGetOutputE(t *testing.T, options *Options, cmd string, additionalArgs ...string) (string, error) { | ||
args := []string{cmd} | ||
args = getCommonArgs(options, args...) |
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 refactor was necessary because only install
and upgrade
take the --set
, --set-file
, and -f
options.
@@ -223,10 +269,18 @@ workflows: | |||
tags: | |||
only: /^v.*/ | |||
|
|||
- helm_test: |
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.
Will this run in parallel?
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.
**NOTE**: we have build tags to differentiate kubernetes tests from non-kubernetes tests, and further differentiate helm | ||
tests. This is done because minikube is heavy and can interfere with docker related tests in terratest. Similarly, helm | ||
can overload the minikube system and thus interfere with the other kubernetes tests. To avoid overloading the system, we | ||
run the kubernetes tests and helm tests separately from the others. |
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 does "overload the system" mean here? Too much CPU or memory consumption? What are the symptoms? Is it just overloading it in CircleCI, and would work OK on larger servers?
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'll update the comment, but here is an example failing test: https://circleci.com/gh/gruntwork-io/terratest/869
Of the 28 tests, 19 fail. They all fail with:
Post https://10.142.2.94:8443/apis/authorization.k8s.io/v1/selfsubjectaccessreviews: dial tcp 10.142.2.94:8443: connect: connection refused
This indicates that either:
- minikube crashed
- minikube failed to respond
I don't think this would be a problem on beefy machines, as this does NOT happen when I run the kubernetes+helm tests locally, so I believe this is an issue with CircleCI. My best guess is memory. The executors have 8GB of RAM, but my laptop is 16GB. I don't think it's CPU because I am on a 13inch no touchbar model, which is a dual core machine.
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.
New verbiage:
// NOTE: we have build tags to differentiate kubernetes tests from non-kubernetes tests. This is done because minikube
// is heavy and can interfere with docker related tests in terratest. Specifically, many of the tests start to fail with
// `connection refused` errors from `minikube`. To avoid overloading the system, we run the kubernetes tests and helm
// tests separately from the others. This may not be necessary if you have a sufficiently powerful machine. We
// recommend at least 4 cores and 16GB of RAM if you want to run all the tests together.
// Install will install the selected helm chart with the provided options under the given release name. This will fail | ||
// the test if there is an error. | ||
func Install(t *testing.T, options *Options, chartDir string, releaseName string) { | ||
require.NoError(t, InstallE(t, options, chartDir, releaseName)) |
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 like this clean, one-line approach 👍
|
||
// GetConfigPath will return a sensible default if the config path is not set on the options. | ||
func (kubectlOptions *KubectlOptions) GetConfigPath(t *testing.T) (string, error) { | ||
var err 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.
Why declare this here, separately?
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'll add comment, but this is so that I can update kubeConfigPath
in the if block below. Otherwise, it complains saying err
is undefined.
modules/k8s/pod.go
Outdated
t, | ||
statusMsg, | ||
60, | ||
1*time.Second, |
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.
Shouldn't you use the passed-in retry settings?
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.
Woops copy pasta from when I was experimenting in helm-kubernetes-services
. Will fix.
modules/k8s/tunnel.go
Outdated
return "svc" | ||
default: | ||
// This should not happen | ||
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.
Perhaps return a clear error message in the string?
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.
Ok
modules/k8s/tunnel.go
Outdated
ResourceName string | ||
Out io.Writer | ||
StopChan chan struct{} | ||
ReadyChan chan struct{} |
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.
Are all these fields are used outside of this package (esp the channels)? If not, you can make them lowercase to make them private.
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.
Ah ok. I didn't know that convention. Will update!
|
||
// GetAvailablePortE retrieves an available port on the host machine. This delegates the port selection to the golang net | ||
// library by starting a server and then checking the port that the server is using. | ||
func GetAvailablePortE(t *testing.T) (int, 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.
If you run this in a concurrent context, couldn't a race condition lead to the same port being selected? i.e., goroutine one listens on port 12345, it's available, so it returns the port number, and in the defer
stops listening. Right after the defer
, but before the port number is returned, another goroutine happens to listen on 12345, see it's available, and returns it.
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 you are right. Let me see if there is a way to make this work correctly.
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.
Ok resolved this by changing it so that the available port selection happens in ForwardPortE
when local port is 0. This way, we can enforce a mutex lock between getting the available port and spawning the listener. See 7440db7 .
modules/k8s/tunnel_test.go
Outdated
|
||
// Open a tunnel to pod from any available port locally | ||
localPort := GetAvailablePort(t) | ||
tunnel := NewTunnel(options, ResourceTypePod, "nginx-pod", localPort, 80) |
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.
Nice, powerful API hiding a lot of complexity 👍
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.
Can't take credit for it since it is copied from helm
😄 But yes I like this API.
30, | ||
10*time.Second, | ||
func(statusCode int, body string) bool { | ||
return statusCode == 200 |
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.
Check the body contains the text "nginx"?
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.
Ok
Ok I believe I addressed all the comments except for the concurrency issue with |
Also verified the private var conversion for |
Ok I believe I addressed all the review comments, and build is passing, so going to go ahead and merge + release this! |
This introduces helm integration tests by providing
helm.Install
andhelm.Delete
functions. Additionally, this extends thek8s
module with additional functions that map better to helm. Specifically, when you install a chart, it is not obvious what name is chosen for the chart. However, you are always guaranteed to have a bunch of labels defined on the resources, so it is more common to query Kubernetes resources using the labels than directly by name with helm.Here is the list of new functions introduced:
helm.Install
: Install a helm chart onto a Kubernetes clusterhelm.Delete
: Delete a deployed release from Tillerk8s.ListPods
: List pods in a given namespacek8s.ListServices
: List services in a given namespacek8s.WaitUntilNumPodsCreated
: Waits until the number of pods matching the given filters reaches the desired count.k8s.Tunnel
: A struct that can be used to create and manage port forward tunnel to a Pod.Finally, I had to further separate out the helm tests because Tiller overloaded minikube when running with the kubernetes tests.