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

Test verify subcommand in Go end-to-end #185

Merged
merged 1 commit into from
Mar 7, 2024
Merged

Conversation

burgerdev
Copy link
Contributor

No description provided.

@burgerdev burgerdev requested a review from 3u13r February 29, 2024 15:03
Base automatically changed from burgerdev/cmd-package to main February 29, 2024 15:42
@burgerdev burgerdev marked this pull request as ready for review February 29, 2024 15:44
Copy link
Member

@katexochen katexochen left a comment

Choose a reason for hiding this comment

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

Otherwise looks good to me. 🦭

Comment on lines 41 to 70
output, err := os.MkdirTemp("", "nunki-verify.*")
require.NoError(err)
t.Cleanup(func() {
_ = os.RemoveAll(output)
})

coordinator, cancelPortforward, err := c.PortForwardPod(ctx, namespace, "port-forwarder-coordinator", "1313")
require.NoError(err)
t.Cleanup(cancelPortforward)

verify := cmd.NewVerifyCmd()
verify.SetArgs([]string{
"--output", output,
"--coordinator-policy-hash=", // TODO(burgerdev): enable policy checking
"--coordinator", coordinator,
})
verify.SetOut(io.Discard) // TODO: do we need it?
errBuf := &bytes.Buffer{}
verify.SetErr(errBuf)

if err := verify.Execute(); err != nil {
t.Log(string(errBuf.Bytes()))
t.Fatalf("could not verify coordinator: %v", err)
}

for _, expected := range []string{"manifest.0.json", "coordinator-root.pem", "mesh-root.pem"} {
_, err := os.Stat(path.Join(output, expected))
assert.NoError(err, "expected verify output to contain file %q", expected)
}

Copy link
Member

Choose a reason for hiding this comment

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

I'd say this shouldn't be part of the OpenSSL test. We should rather create a test suite in TestMain or at least run subtests in this within this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved this into a TestMain, but then realized that there's a bunch of duplication between that and the OpenSSL test proper, so I decided to restructure the Kubeclient a bit.

  • It now owns the k8s namespace (in anticipation of being responsible for creating it later).
  • It exposes a Setup/Teardown pair, to be called by the TestMain.
  • Nunki verify (and later apply and set) are part of Setup (and cleaned up in Teardown).

A bit ugly is how the kubeclient is passed to TestOpenssl (via global var).

3u13r
3u13r previously requested changes Mar 4, 2024
Copy link
Member

@3u13r 3u13r left a comment

Choose a reason for hiding this comment

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

Good Job! the overall architecture looks very usable.

@@ -30,6 +30,13 @@ type Kubeclient struct {
client *kubernetes.Clientset
// config is the "Kubeconfig" for the client
config *rest.Config

// Below fields are only populated by Setup().
Copy link
Member

Choose a reason for hiding this comment

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

I think that this makes the Kubeclient a bit too stateful. At least we should think about separating the Kubernetes parts from the Contrast/testing parts.
Generally, I think it's fine to hold the namespace state at e.g. the testing level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion, it's probably for the better to split these up.

//
// If any setup step fails, Setup returns an error but does not clean up any resources. Call
// Teardown for that.
func (k *Kubeclient) Setup() error {
Copy link
Member

Choose a reason for hiding this comment

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

Since we want to assert the behavior of the Nunki steps contained in here, this should likely be part of the test code. Also this makes more sense, since this code is purpose built for the e2e tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On the other hand, this is the same for all test variants (simple, openssl, enojivoto), and at least set is a prerequisite for running the tests at all. Maybe my idea of putting all of just default into Setup is wrong, and it should rather be Setup and a dedicated test for verify. Let me try to rework that.

e2e/openssl/openssl_test.go Outdated Show resolved Hide resolved
e2e/internal/kubeclient/setup.go Outdated Show resolved Hide resolved
// namespace the tests are executed in.
const namespaceEnv = "K8S_NAMESPACE"
// filled by TestMain
var k *kubeclient.Kubeclient
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that we need global state in the test. Sure, there is sort of global state since we are re-using on AKS, but by using different e.g. namespaces we can keep the tests independent (and also execute them conurrently if we were to scale up the AKS)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have global state, in the sense that the coordinator is shared between tests and set up/torn down in the TestMain (see also @katexochen's requested changes). Sharing the kubeclient just seemed like a convenient way of sharing that state, but I agree that it's less obvious what state is shared.

Copy link
Member

Choose a reason for hiding this comment

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

Talked to Paul and convinced him that it's not a good idea to generally share the coordinator between tests. The reasoning is mostly that the emojivoto test should be completely independent of the openssl test.

Of course we want to re-use the coordinator for (sub-)tests of the same functionality, i.e. 1. Test is calling set and asserting the certs and the 2. test calls set again and also checks that the new mesh certs are not singed by the same Mesh/IntermRoot.

This creates some overhead, mostly in the memory the coordinator is using so we likely want to call those test with --parallel=1.

Copy link
Member

Choose a reason for hiding this comment

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

This creates some overhead, mostly in the memory the coordinator is using so we likely want to call those test with --parallel=1.

Test and subtests in the same package should run sequentially if they don't call t.Parallel(). The rest can be done using the sync server (soon™).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True for subtests, but I don't think execution order is specified for top-level tests.

Copy link
Member

Choose a reason for hiding this comment

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

No order isn't specified, and tests should not rely on order. But still won't run in parallel.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, according to what I found top-level test are run in separate go go routines which execution can overlap. I wasn't sure if we put all e2e tests in the same package, therefore the hint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was reading @3u13r's proposal as having set, verify and specific tests in separate Test functions on the same level, which only works if execution order is guaranteed. Maybe I got that wrong. But we can have ordered subtests to that:

func TestOpenSSL(t *testing.T) {
  c := kubeclient.NewForTest(t)

  // Generate and apply YAML
  
  cli := nunkitest.New(t)
  
  t.Run("set", cli.set)
  t.Run("verify", cli.verify)
  t.Run("openssl-specifics", func(t *testing.T) {
    // exec into backend, etc.
  })
}

Copy link
Member

Choose a reason for hiding this comment

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

Maybe even:

func TestBasicCommands(t *testing.T) {
   c := kubeclient.NewForTest(t)

  // Generate and apply YAML
  
  cli := nunkitest.New(t)
  
  t.Run("set", cli.set)
  t.Run("verify", cli.verify)

  // Teardown
}

func TestOpenSSL(t *testing.T) {
  c := kubeclient.NewForTest(t)

  // Generate and apply YAML

  cli := nunkitest.New(t)
  t.Run("set", cli.set)

  t.Run("openssl test a", func(t *testing.T) {
    // exec into backend, etc.
  })
  t.Run("openssl test b", func(t *testing.T) {
    // exec into backend, etc.
  })

  // Teardown
}

The most important part here is that we get individual feedback from different tests, even if some of them fail. I'm not that sure about the grouping in this case, but we will for sure have tests that should be executed against a separate coordinator, so it might be a good idea to do that for the supported tests now so it is part of our test structure already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, thanks for the discussion everybody. While I revisit that, I would like to advertise a spin off at #191 that should be less contentious and would help me keep things focused here.

@burgerdev
Copy link
Contributor Author

I rewrote this PR to focus on the use of verify for the OpenSSL test case. Next steps should be:

  1. Add set to the Go test code, similarly to how verify is added here.
  2. Factor verify (and set, ...) into a test helper lib to cater the common use cases (e.g. get mesh CA cert). I decided against doing that here because the concrete API is not obvious to me without having seen additional use cases, and I'd like to agree on the overall approach first.

e2e/openssl/openssl_test.go Outdated Show resolved Hide resolved
e2e/openssl/openssl_test.go Outdated Show resolved Hide resolved
This commit implements a first use case for calling the CLI commands
from e2e test code. It is deliberately kept inline to focus on the test
mechanics first. Once we need to call verify from more places, we can
factor it into a test helper library.

Co-authored-by: Paul Meyer <[email protected]>
@burgerdev burgerdev merged commit 5a3ae1e into main Mar 7, 2024
6 checks passed
@burgerdev burgerdev deleted the burgerdev/portforward branch March 7, 2024 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants