diff --git a/tests/e2e/ctl_v3_auth_no_proxy_test.go b/tests/e2e/ctl_v3_auth_no_proxy_test.go index 0b4807c8304e..e84ffd8a2ed0 100644 --- a/tests/e2e/ctl_v3_auth_no_proxy_test.go +++ b/tests/e2e/ctl_v3_auth_no_proxy_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// These tests depends on certificate-based authentication that is NOT supported +// These tests depend on certificate-based authentication that is NOT supported // by gRPC proxy. //go:build !cluster_proxy // +build !cluster_proxy @@ -20,7 +20,10 @@ package e2e import ( + "fmt" + "sync" "testing" + "time" ) func TestCtlV3AuthCertCN(t *testing.T) { @@ -32,3 +35,105 @@ func TestCtlV3AuthCertCNAndUsername(t *testing.T) { func TestCtlV3AuthCertCNAndUsernameNoPassword(t *testing.T) { testCtl(t, authTestCertCNAndUsernameNoPassword, withCfg(*newConfigClientTLSCertAuth())) } + +func TestCtlV3AuthCertCNWithWithConcurrentOperation(t *testing.T) { + BeforeTest(t) + + // apply the certificate which has `root` CommonName, + // and reset the setting when the test case finishes. + // TODO(ahrtr): enhance the e2e test framework to support + // certificates with CommonName. + t.Log("Apply certificate with root CommonName") + resetCert := applyTLSWithRootCommonName() + defer resetCert() + + t.Log("Create an etcd cluster") + cx := getDefaultCtlCtx(t) + cx.cfg = etcdProcessClusterConfig{ + clusterSize: 1, + clientTLS: clientTLS, + initialToken: "new", + } + + epc, err := newEtcdProcessCluster(t, &cx.cfg) + if err != nil { + t.Fatalf("Failed to start etcd cluster: %v", err) + } + cx.epc = epc + cx.dataDir = epc.procs[0].Config().dataDirPath + + defer func() { + if err := epc.Close(); err != nil { + t.Fatalf("could not close test cluster (%v)", err) + } + }() + + t.Log("Enable auth") + authEnableTest(cx) + + // Create two goroutines, one goroutine keeps creating & deleting users, + // and the other goroutine keeps writing & deleting K/V entries. + var wg sync.WaitGroup + wg.Add(2) + errs := make(chan error, 2) + donec := make(chan struct{}) + + // Create the first goroutine to create & delete users + t.Log("Create the first goroutine to create & delete users") + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + user := fmt.Sprintf("testuser-%d", i) + pass := fmt.Sprintf("testpass-%d", i) + + if err := ctlV3User(cx, []string{"add", user, "--interactive=false"}, "User test-user created", []string{pass}); err != nil { + errs <- fmt.Errorf("failed to create user %q: %w", user, err) + break + } + + err := ctlV3User(cx, []string{"delete", user}, fmt.Sprintf("User %s deleted", user), []string{}) + if err != nil { + errs <- fmt.Errorf("failed to delete user %q: %w", user, err) + break + } + } + t.Log("The first goroutine finished") + }() + + // Create the second goroutine to write & delete K/V entries + t.Log("Create the second goroutine to write & delete K/V entries") + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + key := fmt.Sprintf("key-%d", i) + value := fmt.Sprintf("value-%d", i) + + if err := ctlV3Put(cx, key, value, ""); err != nil { + errs <- fmt.Errorf("failed to put key %q: %w", key, err) + break + } + + if err := ctlV3Del(cx, []string{key}, 1); err != nil { + errs <- fmt.Errorf("failed to delete key %q: %w", key, err) + break + } + } + t.Log("The second goroutine finished") + }() + + t.Log("Waiting for the two goroutines to complete") + go func() { + wg.Wait() + close(donec) + }() + + t.Log("Waiting for test result") + select { + case err := <-errs: + t.Fatalf("Unexpected error: %v", err) + case <-donec: + t.Log("All done!") + case <-time.After(30 * time.Second): + t.Fatal("Test case timeout after 20 seconds") + } +} diff --git a/tests/e2e/ctl_v3_auth_test.go b/tests/e2e/ctl_v3_auth_test.go index 9d937c38154d..0792087e0626 100644 --- a/tests/e2e/ctl_v3_auth_test.go +++ b/tests/e2e/ctl_v3_auth_test.go @@ -19,6 +19,7 @@ import ( "fmt" "net/url" "os" + "path/filepath" "syscall" "testing" "time" @@ -100,6 +101,28 @@ func authEnable(cx ctlCtx) error { return nil } +func applyTLSWithRootCommonName() func() { + var ( + oldCertPath = certPath + oldPrivateKeyPath = privateKeyPath + oldCaPath = caPath + + newCertPath = filepath.Join(fixturesDir, "CommonName-root.crt") + newPrivateKeyPath = filepath.Join(fixturesDir, "CommonName-root.key") + newCaPath = filepath.Join(fixturesDir, "CommonName-root.crt") + ) + + certPath = newCertPath + privateKeyPath = newPrivateKeyPath + caPath = newCaPath + + return func() { + certPath = oldCertPath + privateKeyPath = oldPrivateKeyPath + caPath = oldCaPath + } +} + func ctlV3AuthEnable(cx ctlCtx) error { cmdArgs := append(cx.PrefixArgs(), "auth", "enable") return spawnWithExpectWithEnv(cmdArgs, cx.envMap, "Authentication Enabled")