// Copyright 2016 The etcd Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package e2e import ( "fmt" "os" "strings" "testing" "time" "go.etcd.io/etcd/pkg/v3/expect" "go.etcd.io/etcd/tests/v3/framework/e2e" ) func TestCtlV3Lock(t *testing.T) { testCtl(t, testLock) } func TestCtlV3LockWithCmd(t *testing.T) { testCtl(t, testLockWithCmd) } func testLock(cx ctlCtx) { name := "a" holder, ch, err := ctlV3Lock(cx, name) if err != nil { cx.t.Fatal(err) } l1 := "" select { case <-time.After(2 * time.Second): cx.t.Fatalf("timed out locking") case l1 = <-ch: if !strings.HasPrefix(l1, name) { cx.t.Errorf("got %q, expected %q prefix", l1, name) } } // blocked process that won't acquire the lock blocked, ch, err := ctlV3Lock(cx, name) if err != nil { cx.t.Fatal(err) } select { case <-time.After(100 * time.Millisecond): case <-ch: cx.t.Fatalf("should block") } // overlap with a blocker that will acquire the lock blockAcquire, ch, err := ctlV3Lock(cx, name) if err != nil { cx.t.Fatal(err) } defer blockAcquire.Stop() select { case <-time.After(100 * time.Millisecond): case <-ch: cx.t.Fatalf("should block") } // kill blocked process with clean shutdown if err = blocked.Signal(os.Interrupt); err != nil { cx.t.Fatal(err) } if err = e2e.CloseWithTimeout(blocked, time.Second); err != nil { cx.t.Fatal(err) } // kill the holder with clean shutdown if err = holder.Signal(os.Interrupt); err != nil { cx.t.Fatal(err) } if err = e2e.CloseWithTimeout(holder, 200*time.Millisecond+time.Second); err != nil { cx.t.Fatal(err) } // blockAcquire should acquire the lock select { case <-time.After(time.Second): cx.t.Fatalf("timed out from waiting to holding") case l2 := <-ch: if l1 == l2 || !strings.HasPrefix(l2, name) { cx.t.Fatalf("expected different lock name, got l1=%q, l2=%q", l1, l2) } } } func testLockWithCmd(cx ctlCtx) { // exec command with zero exit code echoCmd := []string{"echo"} if err := ctlV3LockWithCmd(cx, echoCmd, ""); err != nil { cx.t.Fatal(err) } // exec command with non-zero exit code code := 3 awkCmd := []string{"awk", fmt.Sprintf("BEGIN{exit %d}", code)} expect := fmt.Sprintf("Error: exit status %d", code) if err := ctlV3LockWithCmd(cx, awkCmd, expect); err != nil { cx.t.Fatal(err) } } // ctlV3Lock creates a lock process with a channel listening for when it acquires the lock. func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, error) { cmdArgs := append(cx.PrefixArgs(), "lock", name) proc, err := e2e.SpawnCmd(cmdArgs, cx.envMap) outc := make(chan string, 1) if err != nil { close(outc) return proc, outc, err } go func() { s, xerr := proc.ExpectFunc(func(string) bool { return true }) if xerr != nil { cx.t.Errorf("expect failed (%v)", xerr) } outc <- s }() return proc, outc, err } // ctlV3LockWithCmd creates a lock process to exec command. func ctlV3LockWithCmd(cx ctlCtx, execCmd []string, as ...string) error { // use command as lock name cmdArgs := append(cx.PrefixArgs(), "lock", execCmd[0]) cmdArgs = append(cmdArgs, execCmd...) return e2e.SpawnWithExpects(cmdArgs, cx.envMap, as...) }