mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	return an error instead of recording a test failure
Signed-off-by: carlory <baofa.fan@daocloud.io>
This commit is contained in:
		@@ -20,6 +20,7 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -144,40 +145,40 @@ func ExecShellInPodWithFullOutput(ctx context.Context, f *framework.Framework, p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyExecInPodSucceed verifies shell cmd in target pod succeed
 | 
			
		||||
func VerifyExecInPodSucceed(ctx context.Context, f *framework.Framework, pod *v1.Pod, shExec string) {
 | 
			
		||||
func VerifyExecInPodSucceed(ctx context.Context, f *framework.Framework, pod *v1.Pod, shExec string) error {
 | 
			
		||||
	stdout, stderr, err := ExecShellInPodWithFullOutput(ctx, f, pod.Name, shExec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		var exitError clientexec.CodeExitError
 | 
			
		||||
		if errors.As(err, &exitError) {
 | 
			
		||||
			exitCode := exitError.ExitStatus()
 | 
			
		||||
			framework.ExpectNoError(err,
 | 
			
		||||
				"%q should succeed, but failed with exit code %d and error message %q\nstdout: %s\nstderr: %s",
 | 
			
		||||
			return fmt.Errorf("%q should succeed, but failed with exit code %d and error message %w\nstdout: %s\nstderr: %s",
 | 
			
		||||
				shExec, exitCode, exitError, stdout, stderr)
 | 
			
		||||
		} else {
 | 
			
		||||
			framework.ExpectNoError(err,
 | 
			
		||||
				"%q should succeed, but failed with error message %q\nstdout: %s\nstderr: %s",
 | 
			
		||||
			return fmt.Errorf("%q should succeed, but failed with error message %w\nstdout: %s\nstderr: %s",
 | 
			
		||||
				shExec, err, stdout, stderr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyExecInPodFail verifies shell cmd in target pod fail with certain exit code
 | 
			
		||||
func VerifyExecInPodFail(ctx context.Context, f *framework.Framework, pod *v1.Pod, shExec string, exitCode int) {
 | 
			
		||||
func VerifyExecInPodFail(ctx context.Context, f *framework.Framework, pod *v1.Pod, shExec string, exitCode int) error {
 | 
			
		||||
	stdout, stderr, err := ExecShellInPodWithFullOutput(ctx, f, pod.Name, shExec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		var exitError clientexec.CodeExitError
 | 
			
		||||
		if errors.As(err, &exitError) {
 | 
			
		||||
			actualExitCode := exitError.ExitStatus()
 | 
			
		||||
			gomega.Expect(actualExitCode).To(gomega.Equal(exitCode),
 | 
			
		||||
				"%q should fail with exit code %d, but failed with exit code %d and error message %q\nstdout: %s\nstderr: %s",
 | 
			
		||||
			if actualExitCode == exitCode {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("%q should fail with exit code %d, but failed with exit code %d and error message %w\nstdout: %s\nstderr: %s",
 | 
			
		||||
				shExec, exitCode, actualExitCode, exitError, stdout, stderr)
 | 
			
		||||
		} else {
 | 
			
		||||
			framework.ExpectNoError(err,
 | 
			
		||||
				"%q should fail with exit code %d, but failed with error message %q\nstdout: %s\nstderr: %s",
 | 
			
		||||
			return fmt.Errorf("%q should fail with exit code %d, but failed with error message %w\nstdout: %s\nstderr: %s",
 | 
			
		||||
				shExec, exitCode, err, stdout, stderr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	gomega.Expect(err).To(gomega.HaveOccurred(), "%q should fail with exit code %d, but exit without error", shExec, exitCode)
 | 
			
		||||
	return fmt.Errorf("%q should fail with exit code %d, but exit without error", shExec, exitCode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func execute(ctx context.Context, method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -510,7 +510,8 @@ func testVolumeContent(ctx context.Context, f *framework.Framework, pod *v1.Pod,
 | 
			
		||||
			framework.ExpectNoError(err, "failed: finding the contents of the block device %s.", deviceName)
 | 
			
		||||
 | 
			
		||||
			// Check that it's a real block device
 | 
			
		||||
			CheckVolumeModeOfPath(ctx, f, pod, test.Mode, deviceName)
 | 
			
		||||
			err = CheckVolumeModeOfPath(ctx, f, pod, test.Mode, deviceName)
 | 
			
		||||
			framework.ExpectNoError(err, "failed: getting the right privileges in the block device %v", deviceName)
 | 
			
		||||
		} else {
 | 
			
		||||
			// Filesystem: check content
 | 
			
		||||
			fileName := fmt.Sprintf("/opt/%d/%s", i, test.File)
 | 
			
		||||
@@ -520,7 +521,8 @@ func testVolumeContent(ctx context.Context, f *framework.Framework, pod *v1.Pod,
 | 
			
		||||
 | 
			
		||||
			// Check that a directory has been mounted
 | 
			
		||||
			dirName := filepath.Dir(fileName)
 | 
			
		||||
			CheckVolumeModeOfPath(ctx, f, pod, test.Mode, dirName)
 | 
			
		||||
			err = CheckVolumeModeOfPath(ctx, f, pod, test.Mode, dirName)
 | 
			
		||||
			framework.ExpectNoError(err, "failed: getting the right privileges in the directory %v", dirName)
 | 
			
		||||
 | 
			
		||||
			if !framework.NodeOSDistroIs("windows") {
 | 
			
		||||
				// Filesystem: check fsgroup
 | 
			
		||||
@@ -663,18 +665,27 @@ func generateWriteFileCmd(content, fullPath string) []string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckVolumeModeOfPath check mode of volume
 | 
			
		||||
func CheckVolumeModeOfPath(ctx context.Context, f *framework.Framework, pod *v1.Pod, volMode v1.PersistentVolumeMode, path string) {
 | 
			
		||||
func CheckVolumeModeOfPath(ctx context.Context, f *framework.Framework, pod *v1.Pod, volMode v1.PersistentVolumeMode, path string) error {
 | 
			
		||||
	if volMode == v1.PersistentVolumeBlock {
 | 
			
		||||
		// Check if block exists
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("test -b %s", path))
 | 
			
		||||
		if err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("test -b %s", path)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Double check that it's not directory
 | 
			
		||||
		e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("test -d %s", path), 1)
 | 
			
		||||
		if err := e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("test -d %s", path), 1); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Check if directory exists
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("test -d %s", path))
 | 
			
		||||
		if err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("test -d %s", path)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Double check that it's not block
 | 
			
		||||
		e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("test -b %s", path), 1)
 | 
			
		||||
		if err := e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("test -b %s", path), 1); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -194,7 +194,8 @@ func (p *ephemeralTestSuite) DefineTests(driver storageframework.TestDriver, pat
 | 
			
		||||
				// attempt to create a dummy file and expect for it not to be created
 | 
			
		||||
				command = "ls /mnt/test* && (touch /mnt/test-0/hello-world || true) && [ ! -f /mnt/test-0/hello-world ]"
 | 
			
		||||
			}
 | 
			
		||||
			e2epod.VerifyExecInPodSucceed(ctx, f, pod, command)
 | 
			
		||||
			err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, command)
 | 
			
		||||
			framework.ExpectNoError(err, "while checking read-only mount")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		l.testCase.TestEphemeral(ctx)
 | 
			
		||||
@@ -214,7 +215,8 @@ func (p *ephemeralTestSuite) DefineTests(driver storageframework.TestDriver, pat
 | 
			
		||||
			if pattern.VolMode == v1.PersistentVolumeBlock {
 | 
			
		||||
				command = "if ! [ -b /mnt/test-0 ]; then echo /mnt/test-0 is not a block device; exit 1; fi"
 | 
			
		||||
			}
 | 
			
		||||
			e2epod.VerifyExecInPodSucceed(ctx, f, pod, command)
 | 
			
		||||
			err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, command)
 | 
			
		||||
			framework.ExpectNoError(err, "while checking read/write mount")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		l.testCase.TestEphemeral(ctx)
 | 
			
		||||
@@ -308,8 +310,10 @@ func (p *ephemeralTestSuite) DefineTests(driver storageframework.TestDriver, pat
 | 
			
		||||
			// visible in the other.
 | 
			
		||||
			if pattern.VolMode != v1.PersistentVolumeBlock && !readOnly && !shared {
 | 
			
		||||
				ginkgo.By("writing data in one pod and checking the second does not see it (it should get its own volume)")
 | 
			
		||||
				e2epod.VerifyExecInPodSucceed(ctx, f, pod, "touch /mnt/test-0/hello-world")
 | 
			
		||||
				e2epod.VerifyExecInPodSucceed(ctx, f, pod2, "[ ! -f /mnt/test-0/hello-world ]")
 | 
			
		||||
				err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, "touch /mnt/test-0/hello-world")
 | 
			
		||||
				framework.ExpectNoError(err, "while writing data in first pod")
 | 
			
		||||
				err = e2epod.VerifyExecInPodSucceed(ctx, f, pod2, "[ ! -f /mnt/test-0/hello-world ]")
 | 
			
		||||
				framework.ExpectNoError(err, "while checking data in second pod")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// TestEphemeral expects the pod to be fully deleted
 | 
			
		||||
 
 | 
			
		||||
@@ -499,7 +499,8 @@ func testAccessMultipleVolumes(ctx context.Context, f *framework.Framework, cs c
 | 
			
		||||
		index := i + 1
 | 
			
		||||
		path := fmt.Sprintf("/mnt/volume%d", index)
 | 
			
		||||
		ginkgo.By(fmt.Sprintf("Checking if the volume%d exists as expected volume mode (%s)", index, *pvc.Spec.VolumeMode))
 | 
			
		||||
		e2evolume.CheckVolumeModeOfPath(ctx, f, pod, *pvc.Spec.VolumeMode, path)
 | 
			
		||||
		err = e2evolume.CheckVolumeModeOfPath(ctx, f, pod, *pvc.Spec.VolumeMode, path)
 | 
			
		||||
		framework.ExpectNoError(err)
 | 
			
		||||
 | 
			
		||||
		if readSeedBase > 0 {
 | 
			
		||||
			ginkgo.By(fmt.Sprintf("Checking if read from the volume%d works properly", index))
 | 
			
		||||
@@ -607,7 +608,8 @@ func TestConcurrentAccessToSingleVolume(ctx context.Context, f *framework.Framew
 | 
			
		||||
	for i, pod := range pods {
 | 
			
		||||
		index := i + 1
 | 
			
		||||
		ginkgo.By(fmt.Sprintf("Checking if the volume in pod%d exists as expected volume mode (%s)", index, *pvc.Spec.VolumeMode))
 | 
			
		||||
		e2evolume.CheckVolumeModeOfPath(ctx, f, pod, *pvc.Spec.VolumeMode, path)
 | 
			
		||||
		err := e2evolume.CheckVolumeModeOfPath(ctx, f, pod, *pvc.Spec.VolumeMode, path)
 | 
			
		||||
		framework.ExpectNoError(err)
 | 
			
		||||
 | 
			
		||||
		if readOnly {
 | 
			
		||||
			ginkgo.By("Skipping volume content checks, volume is read-only")
 | 
			
		||||
@@ -643,7 +645,8 @@ func TestConcurrentAccessToSingleVolume(ctx context.Context, f *framework.Framew
 | 
			
		||||
		index := i + 1
 | 
			
		||||
		// index of pod and index of pvc match, because pods are created above way
 | 
			
		||||
		ginkgo.By(fmt.Sprintf("Rechecking if the volume in pod%d exists as expected volume mode (%s)", index, *pvc.Spec.VolumeMode))
 | 
			
		||||
		e2evolume.CheckVolumeModeOfPath(ctx, f, pod, *pvc.Spec.VolumeMode, "/mnt/volume1")
 | 
			
		||||
		err := e2evolume.CheckVolumeModeOfPath(ctx, f, pod, *pvc.Spec.VolumeMode, "/mnt/volume1")
 | 
			
		||||
		framework.ExpectNoError(err)
 | 
			
		||||
 | 
			
		||||
		if readOnly {
 | 
			
		||||
			ginkgo.By("Skipping volume content checks, volume is read-only")
 | 
			
		||||
 
 | 
			
		||||
@@ -449,25 +449,34 @@ func isSudoPresent(ctx context.Context, nodeIP string, provider string) bool {
 | 
			
		||||
func CheckReadWriteToPath(ctx context.Context, f *framework.Framework, pod *v1.Pod, volMode v1.PersistentVolumeMode, path string) {
 | 
			
		||||
	if volMode == v1.PersistentVolumeBlock {
 | 
			
		||||
		// random -> file1
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, "dd if=/dev/urandom of=/tmp/file1 bs=64 count=1")
 | 
			
		||||
		err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, "dd if=/dev/urandom of=/tmp/file1 bs=64 count=1")
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to write to file1")
 | 
			
		||||
		// file1 -> dev (write to dev)
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=/tmp/file1 of=%s bs=64 count=1", path))
 | 
			
		||||
		err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=/tmp/file1 of=%s bs=64 count=1", path))
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to write to block volume")
 | 
			
		||||
		// dev -> file2 (read from dev)
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=%s of=/tmp/file2 bs=64 count=1", path))
 | 
			
		||||
		err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=%s of=/tmp/file2 bs=64 count=1", path))
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to read from block volume")
 | 
			
		||||
		// file1 == file2 (check contents)
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, "diff /tmp/file1 /tmp/file2")
 | 
			
		||||
		err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, "diff /tmp/file1 /tmp/file2")
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to compare file1 and file2")
 | 
			
		||||
		// Clean up temp files
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, "rm -f /tmp/file1 /tmp/file2")
 | 
			
		||||
		err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, "rm -f /tmp/file1 /tmp/file2")
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to clean up temp files")
 | 
			
		||||
 | 
			
		||||
		// Check that writing file to block volume fails
 | 
			
		||||
		e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("echo 'Hello world.' > %s/file1.txt", path), 1)
 | 
			
		||||
		err = e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("echo 'Hello world.' > %s/file1.txt", path), 1)
 | 
			
		||||
		framework.ExpectNoError(err, "Expected write to block volume to fail")
 | 
			
		||||
	} else {
 | 
			
		||||
		// text -> file1 (write to file)
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("echo 'Hello world.' > %s/file1.txt", path))
 | 
			
		||||
		err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("echo 'Hello world.' > %s/file1.txt", path))
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to write to file1")
 | 
			
		||||
		// grep file1 (read from file and check contents)
 | 
			
		||||
		e2epod.VerifyExecInPodSucceed(ctx, f, pod, readFile("Hello word.", path))
 | 
			
		||||
		err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, readFile("Hello word.", path))
 | 
			
		||||
		framework.ExpectNoError(err, "Failed to read from file1")
 | 
			
		||||
		// Check that writing to directory as block volume fails
 | 
			
		||||
		e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("dd if=/dev/urandom of=%s bs=64 count=1", path), 1)
 | 
			
		||||
		err = e2epod.VerifyExecInPodFail(ctx, f, pod, fmt.Sprintf("dd if=/dev/urandom of=%s bs=64 count=1", path), 1)
 | 
			
		||||
		framework.ExpectNoError(err, "Expected write to directory to fail")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -513,8 +522,10 @@ func CheckReadFromPath(ctx context.Context, f *framework.Framework, pod *v1.Pod,
 | 
			
		||||
 | 
			
		||||
	sum := sha256.Sum256(genBinDataFromSeed(len, seed))
 | 
			
		||||
 | 
			
		||||
	e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=%s %s bs=%d count=1 | sha256sum", pathForVolMode, iflag, len))
 | 
			
		||||
	e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=%s %s bs=%d count=1 | sha256sum | grep -Fq %x", pathForVolMode, iflag, len, sum))
 | 
			
		||||
	err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=%s %s bs=%d count=1 | sha256sum", pathForVolMode, iflag, len))
 | 
			
		||||
	framework.ExpectNoError(err, "Failed to read from %s", pathForVolMode)
 | 
			
		||||
	err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("dd if=%s %s bs=%d count=1 | sha256sum | grep -Fq %x", pathForVolMode, iflag, len, sum))
 | 
			
		||||
	framework.ExpectNoError(err, "Failed to read from %s", pathForVolMode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckWriteToPath that file can be properly written.
 | 
			
		||||
@@ -538,8 +549,10 @@ func CheckWriteToPath(ctx context.Context, f *framework.Framework, pod *v1.Pod,
 | 
			
		||||
 | 
			
		||||
	encoded := base64.StdEncoding.EncodeToString(genBinDataFromSeed(len, seed))
 | 
			
		||||
 | 
			
		||||
	e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("echo %s | base64 -d | sha256sum", encoded))
 | 
			
		||||
	e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("echo %s | base64 -d | dd of=%s %s bs=%d count=1", encoded, pathForVolMode, oflag, len))
 | 
			
		||||
	err := e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("echo %s | base64 -d | sha256sum", encoded))
 | 
			
		||||
	framework.ExpectNoError(err, "Failed to generate sha256sum of encoded data")
 | 
			
		||||
	err = e2epod.VerifyExecInPodSucceed(ctx, f, pod, fmt.Sprintf("echo %s | base64 -d | dd of=%s %s bs=%d count=1", encoded, pathForVolMode, oflag, len))
 | 
			
		||||
	framework.ExpectNoError(err, "Failed to write to %s", pathForVolMode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSectorSize returns the sector size of the device.
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,8 @@ func (t *VolumeModeDowngradeTest) Setup(ctx context.Context, f *framework.Framew
 | 
			
		||||
	framework.ExpectNoError(err)
 | 
			
		||||
 | 
			
		||||
	ginkgo.By("Checking if PV exists as expected volume mode")
 | 
			
		||||
	e2evolume.CheckVolumeModeOfPath(ctx, f, t.pod, block, devicePath)
 | 
			
		||||
	err = e2evolume.CheckVolumeModeOfPath(ctx, f, t.pod, block, devicePath)
 | 
			
		||||
	framework.ExpectNoError(err)
 | 
			
		||||
 | 
			
		||||
	ginkgo.By("Checking if read/write to PV works properly")
 | 
			
		||||
	storageutils.CheckReadWriteToPath(ctx, f, t.pod, block, devicePath)
 | 
			
		||||
@@ -118,7 +119,8 @@ func (t *VolumeModeDowngradeTest) Test(ctx context.Context, f *framework.Framewo
 | 
			
		||||
	<-done
 | 
			
		||||
 | 
			
		||||
	ginkgo.By("Verifying that nothing exists at the device path in the pod")
 | 
			
		||||
	e2epod.VerifyExecInPodFail(ctx, f, t.pod, fmt.Sprintf("test -e %s", devicePath), 1)
 | 
			
		||||
	err := e2epod.VerifyExecInPodFail(ctx, f, t.pod, fmt.Sprintf("test -e %s", devicePath), 1)
 | 
			
		||||
	framework.ExpectNoError(err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Teardown cleans up any remaining resources.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user