def test_timeout(tmp_path: Path): """ make sure exec runs are handled well when the pod times out and we provide output of the command in the exception """ tmp_path.joinpath("test").write_text("test") m_dir = MappedDir(tmp_path, SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) # we are going to trick sandcastle into thinking we are using the default command # but we are not, b/c we don't want to wait 30 minutes for it time out in CI o.set_pod_manifest(["sleep", "7"]) flexmock(Sandcastle).should_receive("set_pod_manifest").and_return( None).once() o.run() try: # sadly, openshift does not tell us in any way that the container finished # and that's why our exec got killed with pytest.raises(SandcastleCommandFailed) as e: # run a long running command and watch it get killed o.exec( command=["bash", "-c", "while true; do date; sleep 1; done"]) assert "Command failed" in str(e.value) assert e.value.rc == 137 assert e.value.output # we wanna be sure there was some output finally: o.delete_pod()
def test_md_e2e(tmp_path, git_url, branch): # running in k8s if "KUBERNETES_SERVICE_HOST" in os.environ: t = Path(SANDCASTLE_MOUNTPOINT, f"clone-{get_timestamp_now()}") else: t = tmp_path m_dir = MappedDir(t, SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) run_command(["git", "clone", "-b", branch, git_url, t]) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) o.run() try: o.exec(command=["packit", "--debug", "srpm"]) assert list(t.glob("*.src.rpm")) o.exec(command=["packit", "--help"]) with pytest.raises(SandcastleCommandFailed) as ex: o.exec(command=["bash", "-c", "echo 'I quit!'; exit 120"]) e = ex.value assert "I quit!" in e.output assert 120 == e.rc assert "command terminated with non-zero exit code" in e.reason finally: o.delete_pod()
def test_md_exec(tmp_path, cmd, should_fail): """ make sure commands are exec'd properly in the sandbox with mapped dirs this is what we use in p-s with RW vols """ # something needs to be inside tmp_path.joinpath("dummy.file").write_text("something") m_dir = MappedDir(tmp_path, SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) o.run() try: if should_fail: with pytest.raises(SandcastleCommandFailed) as ex: o.exec(command=cmd) assert "No such file or directory\n" in ex.value.output assert "ls: cannot access " in ex.value.output assert isinstance(ex.value, SandcastleCommandFailed) assert "2" in ex.value.reason assert "ExitCode" in ex.value.reason assert "NonZeroExitCode" in ex.value.reason assert ex.value.rc == 2 else: o.exec(command=cmd) finally: o.delete_pod()
def test_basic_e2e_inside(): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, pod_name="lllollz") try: o.run(command=["ls", "-lha"]) finally: o.delete_pod()
def test_exec_succ_pod(tmp_path): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) # we mimic here that the pod has finished and we are still running commands inside o.run(command=["true"]) try: with pytest.raises(SandcastleTimeoutReached) as e: o.exec(command=["true"]) assert "timeout" in str(e.value) finally: o.delete_pod()
def test_pod_sa_not_in_sandbox(tmp_path): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) sa_path = "/var/run/secrets/kubernetes.io/serviceaccount" with pytest.raises(SandcastleCommandFailed) as e: o.run(command=["ls", "-lha", sa_path]) try: assert (e.value.output.strip() == f"ls: cannot access '{sa_path}': No such file or directory") assert e.value.rc == 2 finally: o.delete_pod()
def test_command_long_output(tmp_path): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) o.run() command = ["cat", "/etc/services"] try: out = o.exec(command=command) finally: o.delete_pod() # random strings from /etc/services: beginning, middle, end assert "ssh" in out assert "7687/tcp" in out assert "RADIX" in out
def test_run_failure(): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) try: with pytest.raises(SandcastleCommandFailed) as ex: o.run(command=["ls", "/hauskrecht"]) assert (ex.value.output == "ls: cannot access '/hauskrecht': No such file or directory\n") assert isinstance(ex.value, SandcastleCommandFailed) assert "'exit_code': 2" in ex.value.reason assert "'reason': 'Error'" in ex.value.reason assert ex.value.rc == 2 finally: o.delete_pod()
def test_file_got_changed(tmp_path): m_dir = MappedDir(tmp_path, SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) p = m_dir.local_dir.joinpath("qwe") p.write_text("Hello, Tony!") o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) o.run() try: o.exec(command=["bash", "-c", "echo '\nHello, Tony Stark!' >>./qwe"]) assert "Hello, Tony!\nHello, Tony Stark!\n" == p.read_text() finally: o.delete_pod()
def test_ro_mount(): """test that provided volume is mounted read-only in the sandbox""" p = Path("/asdqwe") volume_name = "fancy-volume" # by default the volume is an emptyDir PV type vs = VolumeSpec(path=p, volume_name=volume_name, read_only=True) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, volume_mounts=[vs]) o.run() try: with pytest.raises(SandcastleCommandFailed) as ex: o.exec(command=["touch", "/asdqwe/path"]) assert "cannot touch '/asdqwe/path': Read-only file system" in ex.value.output finally: o.delete_pod()
def test_lost_found_is_ignored(tmp_path): tmp_path.joinpath("lost+found").mkdir() tmp_path.joinpath("file").write_text("asd") m_dir = MappedDir(tmp_path, SANDCASTLE_MOUNTPOINT) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) o.run() try: o.exec(command=["ls", "-lha", "./"]) with pytest.raises(SandcastleCommandFailed) as ex: o.exec(command=["ls", "./lost+found"]) assert "No such file or directory" in str(ex.value) finally: o.delete_pod()
def test_exec_env(): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) o.run() try: env_list = o.exec(command=["bash", "-c", "env"], env={ "A": None, "B": "", "C": "c", "D": 1 }) assert "A=\n" in env_list assert "B=\n" in env_list assert "C=c\n" in env_list assert "D=1\n" in env_list finally: o.delete_pod()
def test_exec(cmd, should_fail): o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) o.run() try: if should_fail: with pytest.raises(SandcastleCommandFailed) as ex: o.exec(command=cmd) assert "No such file or directory\n" in ex.value.output assert "ls: cannot access " in ex.value.output assert isinstance(ex.value, SandcastleCommandFailed) assert "2" in ex.value.reason assert "ExitCode" in ex.value.reason assert "NonZeroExitCode" in ex.value.reason assert ex.value.rc == 2 else: assert o.exec(command=cmd) finally: o.delete_pod()
def test_md_multiple_exec(tmp_path): tmp_path.joinpath("stark").mkdir() tmp_path.joinpath("qwe").write_text("Hello, Tony!") m_dir = MappedDir(tmp_path, SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) o.run() try: out = o.exec(command=["ls", "./qwe"]) assert "qwe" in out o.exec(command=["touch", "./stark/asd"]) assert tmp_path.joinpath("stark/asd").is_file() o.exec(command=["touch", "./zxc"]) assert tmp_path.joinpath("zxc").is_file() finally: o.delete_pod()
def test_user_is_set(tmp_path): """ verify that $HOME is writable and commands are executed using a user which has an passwd entry """ o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE) o.run() try: assert o.exec(command=["getent", "passwd", "sandcastle"]).startswith( "sandcastle:x:") assert o.exec(command=["id", "-u", "-n"]).strip() == "sandcastle" assert o.exec(command=[ "bash", "-c", "touch ~/.i.want.to.write.to.home " "&& ls -l /home/sandcastle/.i.want.to.write.to.home", ]) finally: o.delete_pod()
def test_md_e2e(tmpdir, git_url, branch): # running in k8s if "KUBERNETES_SERVICE_HOST" in os.environ: t = Path(SANDCASTLE_MOUNTPOINT).joinpath(f"clone-{get_timestamp_now()}") else: t = Path(tmpdir) m_dir = MappedDir(str(t), SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) run_command(["git", "clone", "-b", branch, git_url, t]) o = Sandcastle( image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir ) o.run() try: o.exec(command=["packit", "--debug", "srpm"]) assert list(t.glob("*.src.rpm")) o.exec(command=["packit", "--help"]) finally: o.delete_pod()
def test_dir_sync(tmpdir): p = Path("/asdqwe") vs = VolumeSpec(path=str(p), pvc_from_env="SANDCASTLE_PVC") o = Sandcastle( image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, volume_mounts=[vs] ) o.run() d = p.joinpath("dir") d.mkdir() d.joinpath("file").write_text("asd") try: o.exec(command=["bash", "-c", "ls -lha /asdqwe/dir/file"]) o.exec(command=["bash", "-c", "[[ 'asd' == $(cat /asdqwe/dir/file) ]]"]) o.exec(command=["bash", "-c", "mkdir /asdqwe/dir/d"]) o.exec(command=["bash", "-c", "touch /asdqwe/dir/f"]) assert Path("/asdqwe/dir/d").is_dir() assert Path("/asdqwe/dir/f").is_file() finally: o.delete_pod()
def test_md_new_namespace(tmpdir): t = Path(tmpdir) m_dir = MappedDir(str(t), SANDCASTLE_MOUNTPOINT, with_interim_pvc=True) d = t.joinpath("dir") d.mkdir() d.joinpath("file").write_text("asd") # running within openshift namespace = os.getenv("SANDCASTLE_TESTS_NAMESPACE") if not namespace: # running on a host - you can't create new projects from inside a pod namespace = f"sandcastle-tests-{get_timestamp_now()}" c = ["oc", "new-project", namespace] run_command(c) try: o = Sandcastle( image_reference=SANDBOX_IMAGE, k8s_namespace_name=namespace, mapped_dir=m_dir, ) o.run() try: o.exec(command=["ls", "-lha", f"./dir/file"]) assert d.joinpath("file").read_text() == "asd" cmd = [ "bash", "-c", "curl -skL https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/metrics", ] out = o.exec(command=cmd) j = json.loads(out) # a small proof we are safe assert j["reason"] == "Forbidden" finally: o.delete_pod() finally: if not os.getenv("SANDCASTLE_TESTS_NAMESPACE"): run_command(["oc", "delete", "project", namespace]) run_command(["oc", "project", NAMESPACE])
def test_changing_mode(tmp_path): # running in k8s if "KUBERNETES_SERVICE_HOST" in os.environ: t = Path(SANDCASTLE_MOUNTPOINT) else: t = tmp_path m_dir = MappedDir(t, SANDCASTLE_MOUNTPOINT) fi = t.joinpath("file") fi.write_text("asd") fi.chmod(mode=0o777) fi2 = t.joinpath("file2") fi2.write_text("qwe") fi2.chmod(mode=0o755) di = t.joinpath("dir") di.mkdir() di.chmod(mode=0o775) o = Sandcastle(image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=m_dir) o.run() try: out = o.exec(command=["stat", "-c", "%a", "./file"]).strip() assert "777" == out stat_oct = oct(fi.stat().st_mode)[-3:] assert stat_oct == "777" out = o.exec(command=["stat", "-c", "%a", "./file2"]).strip() assert "755" == out stat_oct = oct(fi2.stat().st_mode)[-3:] assert stat_oct == "755" out = o.exec(command=["stat", "-c", "%a", "./dir"]).strip() assert "775" == out stat_oct = oct(di.stat().st_mode)[-3:] assert stat_oct == "775" finally: purge_dir_content(t) o.delete_pod()
def test_packit_usecase(tmp_path: Path): """invoke sandcastle the same way packit invokes it""" tmp_vol_name = "foobor" tmp_vol_target_path = "/bort-simpson" tmp_dir = tmp_path.joinpath("dir") tmp_dir.mkdir() # let's put some files in there for fi in Path("/etc/systemd/").glob("*.conf"): copy2(fi, tmp_dir) md = MappedDir( local_dir=tmp_path, path=SANDCASTLE_MOUNTPOINT, with_interim_pvc=True, ) vols = [VolumeSpec(path=tmp_vol_target_path, volume_name=tmp_vol_name)] s = Sandcastle( image_reference=SANDBOX_IMAGE, k8s_namespace_name=NAMESPACE, mapped_dir=md, volume_mounts=vols, ) s.run() try: # making sure the files were correctly copied to the mapped dir out = s.exec(command=[ "bash", "-c", f"ls -1 {SANDCASTLE_MOUNTPOINT}/quay-io-packit-sandcastle*/dir/", ]) # making sure we can create files in the temporary volume s.exec(command=["cp", "-a", "/etc/bashrc", tmp_vol_target_path]) finally: s.delete_pod() assert "system.conf" in out