-
Notifications
You must be signed in to change notification settings - Fork 123
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
Run the integration tests using Ginkgo and GHA #536
Run the integration tests using Ginkgo and GHA #536
Conversation
74d12aa
to
eb3c168
Compare
Use a Matrix to test different features in parallel Full Medusa test with backup and restore using MinIO and S3 Full Reaper test scenario
eb3c168
to
f20da62
Compare
cd tests/integration | ||
|
||
# Medusa on S3 requires a secret. Skip step if the secret is absent. | ||
if [[ ( -n "${{ secrets.MEDUSA_SECRET }}" && "${{ matrix.scenario }}" == "Medusa on S3" ) \ |
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 should be in the if:
clause above so the status shows correctly that it was passed.
@@ -0,0 +1,12 @@ | |||
module github.com/k8ssandra/k8ssandra/tests/integration |
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 current approach was to use a single go.mod for everything, or at least everything for every test. We don't want the unit tests and integration tests to diverge in their modules.
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.
That sounds a bit contrary to what we do for the separation of the tests themselves. @burmanm can you explain a bit why if we have dependencies only for unit tests, and some for integration tests, why would we want to lump them all together making it more difficult to track specific dependencies?
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 norm is a single module per project. See https://github.com/golang/go/wiki/Modules#modules.
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.
With respect to test separation in k8s projects, the following structure is pretty common:
- Unit tests live in the same directory as the code under test
- Integration tests that use envtest also live in the same directory as the code under test; however, I think some separation might be worth considering to make it clear that these are not unit tests.
- End-to-end tests that run against a live cluster live under
<project-root>/test/e2e
.
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.
We can rename our mentions of integration
to e2e
if that complies better with Go/k8s terminology, no worries.
Anyone against doing so?
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 the idea of keeping unit
, integration
, and e2e
separate. Something like:
<project-root>/test/unit
(single scoped verifications)
<project-root>/test/integration
(not using live cluster)
<project-root>/test/e2e
(using live cluster - more blackbox style)
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 remove the submodule, I thought it was something we wanted here because of dependency conflicts (although VSCode gave me a hard time with it).
I struggle a bit with the distinction between integration tests and e2e tests 🤔
How would the tests implemented in this PR qualify to you between the two?
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.
My experience has been that integration
testing is verifying an integration between two components typically. That could be done without a live cluster and may include more mocks/spys as needed.
-
Testing
A+B
andB+C
gives some confidence forA->C
but the tests are written a bit differently in that they are testing at a lower level of integration. -
E2E is more of a user experience something like input @
A -> C
(verify output at end) which can be more black box in nature.
This doc is a good reference for differences at these levels.
@@ -1,4 +1,4 @@ | |||
package integration_test | |||
package integration |
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?
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 was matching the directory name. It makes it easier IMO to understand where packages live by just checking the directories layout.
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 has a meaning though in go's packages (please read it). It still has to follow directory / directory_test structure.
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 read about this, thanks for the pointer.
But it doesn't seem to apply here as we're not really testing an integration
package (which would require then to use integration_test
).
I've checked the package for the e2e tests of Kubernetes and it's called e2e, not e2e_test.
Am I misunderstanding your point here? What would we get by naming the package integration_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.
I wanted you to understand the change before making them, instead of making changes without understanding the effect. We have no need for _test in this case.
medusaMinioTestKeyspace = "medusa" | ||
) | ||
|
||
var _ = Describe("Medusa on MinIO", func() { |
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.
Wasn't the plan to ditch ginkgo?
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.
Based on prior discussions, I was also under the impression that we were going to just using the testing
package probably along with https://github.com/stretchr/testify.
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.
@jsanda and @adejanovski I propose an informal write-up for the prior discussions about our testing approach, so we are all on the same page. I can see where Ginkgo
from an integration standpoint may still be viable w/ a more direct approach for the unit tests.
Looking at this medusa_minio_integration_test.go
file, it is extremely readable, easy-to-understand, and self-documenting.
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 propose an informal write-up for the prior discussions about our testing approach
An excellent suggestion and I think that would be great in keeping with the spirit of open source. Maybe we can try to do it with GH Discussions?
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.
Decision pending. Discussion taking place here.
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.
We already decided against BDD, lets not decide multiple times the same thing. Seriously.
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.
Actually we did not. We decided against GoDog, but not everyone is on the same page about BDD vs testing
.
I agree though it's time to build a consensus, which yet has to happen.
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 whole meeting was about BDD, including our guest appearance. We need a reason to use BDD or framework at all beyond testing.T and there simply isn't any (and nothing in this PR has convinced more than the opposite).
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 one thing that would help me (and possibly others), if we could review a set of k8s non-BDD
Go tests used for integration and e2e testing to understand what that fully looks like with all the complexities.
My concern is that these frameworks & libs (Ginkgo, Terratest, Gomega, etc.) were built to ease the pain and understanding of tests at the integration and e2e testing levels.
|
||
var _ = Describe("Medusa on S3", func() { | ||
Context("Deploy K8ssandra with Medusa", func() { | ||
It("Create a kind cluster and a namespace", func() { |
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.
Same code as above test, lets not repeat these steps. This should be createCluster()
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.
Sounds good 👍
ICanSeeTheNamespaceInTheListOfNamespacesStep() | ||
}) | ||
It("Create the Medusa secret ", func() { | ||
IDeployMinIOUsingHelmAndCreateTheBucketStep("k8ssandra-medusa") |
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.
These function names are too-Springish. Go prefers short function names.
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 we go with a less descriptive approach to our testing, for example removal of Ginkgo, the function naming will become critical to understanding any of what is going on. I suspect we will have to have long function names and/or numerous comments.
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.
While this is subjective I tend to agree that the function names do not seem like very idiomatic Go.
We should aim to have descriptive function names regardless of whether we use Ginkgo, testing, or something else.
Readable, descriptive test code is definitely important, but I feel we are getting hung up some things that feel a bit academic right now. I would like to see more discussion and focus around things like:
- How do setup/tear down things?
- How should we verify various state?
- What clients should we use to talk to the api server?
- How do we run things in parallel for CI?
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.
These functions are inherited from the GoDog experiments which auto-generated function names, I didn't named them myself ;)
We can definitely make them shorter, but it would indeed be good to keep them descriptive enough (Go or not Go, I think that should apply to all languages).
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.
Long doesn't always indicate descriptive as it makes reading them a lot slower. Also it should not have information that's obvious.
) | ||
|
||
var ( | ||
medusaS3TestTable = "medusa_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.
Why are these separate from minioTestTable? Why not simply const( testTable = "medusa_test") in a single file?
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'm a Go noob, so trying to make things work as my main goal. Happy to refactor as suggested, it's just that I'm not sure yet what's the variables scope, especially in the context of Ginkgo.
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 ginkgo modifies scopes, then it shouldn't be used.
It("Check the presence of expected resources", func() { | ||
ICanCheckThatResourceOfTypeWithLabelIsPresentInNamespaceStep("service", "app.kubernetes.io/managed-by=reaper-operator") | ||
ICanCheckThatResourceOfTypeWithNameIsPresentInNamespaceStep("service", "k8ssandra-dc1-all-pods-service") | ||
ICanCheckThatResourceOfTypeWithNameIsPresentInNamespaceStep("service", "k8ssandra-dc1-service") |
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 couldn't I check? I know the function takes type, name as parameters. Simply: checkResourceExists("service", "kassandra-dc1-service")
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.
Again, this is inherited from GoDog which generated function names based on the Gherkin steps.
return string(outb.String()) | ||
} | ||
|
||
func GetServicesWithLabel(label string) (*v1.ServiceList, 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.
Go does not use "Get" as prefix.
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 propose we create a basic Go standards guide
to be on the same page. We have a few folks that are learning Go and have background with numerous other languages where it is acceptable to have getters/setters.
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.
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 propose we create a basic Go standards guide to be on the same page.
Effective Go should definitely be the starting point.
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.
It's not really a getter since username/password are not class members (or anything equivalent in Go). I'll rename to "Extract..." since it's more accurate with what is being done.
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
var namespace = "k8ssandra" |
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.
const
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.
It gets changed afterwards. I'll rather not set a value since it won't be used, which is the misleading part here.
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.
It's a global parameter? Ugh.
|
||
// Wait for cass-operator pod to be ready | ||
attempts := 0 | ||
maxAttempts := 10 |
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 not for i := 0; i < maxAttempts; i++ {
and exit if done?
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.
Also, if using gomega, you should use Eventually func. And --wait
should not exit at all, so why multiple attempts?
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.
Actually, why on earth this part at all?
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 on earth this part at all?
Please be welcoming and positive in your comments. No project wants contributors to feel offended.
If you want to teach me something, which is fine as I'm eager to learn, do it nicely. Thanks.
Eventually
is a great alternative that I didn't know of indeed. It'll make my clunky code much cleaner and efficient.
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.
No, what I mean is that why do we have a part that repeats wait? Why not just set a longer timeout?
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 includes any unnecessary use of Eventually also.
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.
There are bits where multiple attempts are needed and we don't have a blocking call such as kubectl wait
.
In order to remove the magic sleep during restore, I'm waiting on cassandra operator progress to change to "Updating" and then "Ready" for example, which requires to repeat the test as long as we don't reach the expected value.
I'll push another commit soon with these changes and you can see if that matches your expectations.
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 would cass-operator require that?
Isn't
k8s.RunKubectl(GinkgoT(), kubectlOptions, "wait", "--for=condition=Ready", "cassandradatacenter/dc1", "--timeout=600s")
doing exactly that? Waiting for the condition to change to Ready?
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.
Actually not, because when restoring a backup, the cassandradatacenter already has the Ready condition but then gets updated by cass-operator.
Just checking for the Ready condition made the wait
command to return instantly, without giving time for the Cassandra pods to be recreated and failed afterwards when trying to access Cassandra using cqlsh.
That's why I initially added the sleep...
So checking first for a state change (in this case detect that cass-operator is updating the cassandradatacenter) and then wait for the dc to be ready allows to remove the sleep and follow the chain of events properly.
|
||
func WaitForPodWithLabelToBeReady(label string, waitTime time.Duration, maxAttempts int) { | ||
kubectlOptions := k8s.NewKubectlOptions("", "", namespace) | ||
attempts := 0 |
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 whole function should be the last command and only last command.
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.
Yup, moving all these retry based checks to use Eventually
now.
} | ||
|
||
var ( | ||
Info = Yellow |
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 these supposed to be mutable strings or enums or what?
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.
Copy/pasted this from a webpage after searching for ways to print colored log lines.
I'd go with enums here as they don't need to mutate.
I guess I should use const
instead then, right?
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.
With enums you would use iota.
} | ||
helm.Install(GinkgoT(), helmOptions, restoreChartPath, "restore-test") | ||
// Give a little time for the cassandraDatacenter resource to be recreated | ||
time.Sleep(60 * 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.
I don't like hardcoded sleeps, they smell like ducttape.
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.
Agreed, using Eventually
should remove the need for this.
attempts := 0 | ||
for { | ||
attempts++ | ||
response, err := restClient.R().Get("http://repair.localhost:8080/cluster") |
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.
We have reaperClient in https://github.com/k8ssandra/reaper-client-go which you can use.
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.
And if it lacks some feature you need for testing, add there.
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.
Neat, I didn't know about this client. Will definitely use it instead.
log.Println(fmt.Sprintf("The HTTP request failed with error %s", err)) | ||
} else { | ||
data := response.Body() | ||
log.Println(fmt.Sprintf("Reaper response: %s", data)) |
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 of these should probably be log.Printf(..)
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.
Very good point 👍
All in all, I see no testing.T modifications. Ginkgo shouldn't be here. |
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 made a first pass through. There is a lot to cover so I will have more feedback in follow up reviews.
Good stuff!
|
||
var _ = Describe("Medusa on MinIO", func() { | ||
Context("Deploy K8ssandra with Medusa", func() { | ||
It("Create a kind cluster and a namespace", func() { |
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 don't want the tests themselves to create the cluster. Doing so makes it more difficult to target different clusters. We definitely want the kind cluster setup, but it should be done separately. Tests should assume that there is a running cluster to which they have access.
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.
Maybe we could make the code more versatile to handle different situations. Having tests handle cleanup/creation is very handy to avoid having to wrap executions in further scripts.
I agree that we need to be able to run integration tests on kind, k3d, minikube, etc... but also GKE, EKS, etc...
We also need to account for limited resources available when running these integration tests, which don't allow us to create several namespaces that would host different k8ssandra clusters. We need to do some cleanup. I've noticed that deleting namespaces could take a while, probably even longer than deleting a cluster with kind and recreating it, so I'm not sure what's the best way to handle this init phase.
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.
Deleting namespace (or actually, uninstalling our installation first) is a important test though.
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.
We also need to account for limited resources available when running these integration tests
We indeed do, but I also do not want to require someone to run kind to run the tests. I think it is perfectly reasonable to run against a GKE cluster for example. I would prefer a separate Makefile target for creating the kind cluster.
We need to do some cleanup. I've noticed that deleting namespaces could take a while, probably even longer than deleting a cluster with kind and recreating it, so I'm not sure what's the best way to handle this init phase.
Resource cleanup definitely needs to happen in the tests. Note that some resources are cluster scoped so just deleting the namespace for example is not sufficient.
ICreateTheMedusaSecretInTheNamespaceApplyingTheFileStep("secret/medusa_minio_secret.yaml") | ||
ICanSeeTheSecretInTheListOfSecretsInTheNamespaceStep("medusa-bucket-key") | ||
}) | ||
It("Install K8ssandra with Medusa on MinIO", func() { |
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.
There is a problem with the test structure having all of these separate specs. If one spec fails, the subsequent specs are most likely going to fail as well. Then they just become noise and make the test run longer than it needs to be.
The easiest way to do it with Ginkgo is to combine all of these specs and use the By
function to provide additional context. The text you pass to By
is logged as well.
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.
Actually, I consider that such e2e/integration/functional tests should fail as soon as one step fails. It's, IMO, how BDD testing should be. One step expects the previous steps to have succeeded, otherwise we're not in the state we expect to be.
If I can't get the cassandradatacenter resource to be ready, then there's no point in trying to access Cassandra or run a repair.
What you describe is more in line with how unit testing should be done where you can reduce the feedback loop by being able to see all failing tests within a single run.
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.
Yes, but that's not what happens. The It
parts are ran by ginkgo even if previous one fails.
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 actually use Ginkgo's failFast flag in CI (and when running the tests locally) to prevent subsequent tests from running after a failure.
Here's the exact command used to run the MinIO tests for example:
go test . -timeout 30m -ginkgo.failFast --ginkgo.trace --ginkgo.progress --ginkgo.slowSpecThreshold=900 --ginkgo.v --ginkgo.focus="Medusa on MinIO" -v -p 1
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 not used -ginkgo.failFast
before. That definitely helps. I wonder if that applies across the whole test suite or just within the current container. I hope the latter. I'll check that out.
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 I can't get the cassandradatacenter resource to be ready, then there's no point in trying to access Cassandra or run a repair.
Absolutely. Once the CassandraDatacenter is ready though, we can perform multiple tests/verifications independently and possibly in parallel. For example, the C* cluster should be automatically registered in Reaper. If that check fails, it should not prevent us from checking that Stargate is deployed.
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 just ran the following using -ginkgo.failFast
:
var _ = FDescribe("A fake test", func() {
Context("running multiple tests in context 1", func() {
It("test 1", func() {
Expect(1).To(Equal(11))
})
It("test 2", func() {
Expect(1).To(Equal(2))
})
})
Context("running multiple tests in context 2", func() {
It("test 3", func() {
Expect(1).To(Equal(3))
})
It("test 4", func() {
Expect(1).To(Equal(3))
})
})
})
Here is the output:
• Failure [0.000 seconds]
A fake test
/Users/jsanda/Development/k8ssandra/k8ssandra/tests/unit/fake_test.go:8
running multiple tests in context 1
/Users/jsanda/Development/k8ssandra/k8ssandra/tests/unit/fake_test.go:9
test 1 [It]
/Users/jsanda/Development/k8ssandra/k8ssandra/tests/unit/fake_test.go:10
Expected
<int>: 1
to equal
<int>: 11
/Users/jsanda/Development/k8ssandra/k8ssandra/tests/unit/fake_test.go:11
------------------------------
It appears to apply across the entire test suite vs only the parent container.
return string(outb.String()) | ||
} | ||
|
||
func GetServicesWithLabel(label string) (*v1.ServiceList, 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 propose we create a basic Go standards guide to be on the same page.
Effective Go should definitely be the starting point.
|
||
func GetServicesWithLabel(label string) (*v1.ServiceList, error) { | ||
kubectlOptions := k8s.NewKubectlOptions("", "", namespace) | ||
clientset, err := k8s.GetKubernetesClientFromOptionsE(GinkgoT(), kubectlOptions) |
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.
We have multiple options when it comes to which clients to use to access the api server. I have a strong preference for using the controller-runtime client. It is easy to use with a pretty small API. More importantly, it is the same client that we use extensively in operator code. You will see controller-runtime used all throughout cass-operator, reaper-operator, medusa-operator, and most operator projects. For an example of its usage in tests take a look at https://github.com/k8ssandra/reaper-operator/tree/master/test.
} | ||
|
||
// Wait for CassandraDatacenter to be ready.. | ||
k8s.RunKubectl(GinkgoT(), kubectlOptions, "wait", "--for=condition=Ready", "cassandradatacenter/dc1", "--timeout=1800s") |
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 wait
package from https://github.com/kubernetes/apimachinery is typically used in e2e tests for polling. See https://github.com/k8ssandra/reaper-operator/blob/master/test/framework/framework.go#L129 for an example.
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.
Your suggested alternative looks more verbose, unless I'm missing something.
Could you elaborate on how using apimachinery is better than relying on kubectl wait
?
Is it because you'd like to remove Terratest?
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.
Using wait
or Eventually
is definitely more verbose but it allows us to log updates which I find helpful when polling for a long time. The particular example I referenced unfortunately does not do any logging, but this one does.
cqlCredentials := GetUsernamePassword("k8ssandra-superuser", namespace) | ||
kubectlOptions := k8s.NewKubectlOptions("", "", namespace) | ||
// Get reaper service | ||
output, _ := k8s.RunKubectlAndGetOutputE(GinkgoT(), kubectlOptions, "exec", "-it", "k8ssandra-dc1-default-sts-0", "--", "/opt/cassandra/bin/cqlsh", "--username", cqlCredentials.username, "--password", cqlCredentials.password, "-e", query) |
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 exec
command is interesting. The api server provides a REST API. exec
is a subresource of Pod. While I do want to use controller-runtime, it doesn't provide support for making exec
requests. We would need to use client-go directly. That would be my preference over terratest but something can tackle later. :)
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.
Sounds good 👍
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.
Given our test direction discussions today, I'm guessing this will be migrated into a different form w/out Ginkgo.
closing in favor of #544 |
Use a Matrix to test different features in parallel
Full Medusa test with backup and restore using MinIO and S3
Full Reaper test scenario
What this PR does:
Adds a first set of integration tests for Medusa and Reaper deployments and introduces a GHA workflow for running them on push.
Which issue(s) this PR fixes:
Fixes #104
Checklist