Skip to content

Commit 0ee7418

Browse files
committed
testhelper: introduce class SMBClient using pysmb
We use the python module pysmb to access the SMB server. The helper class hides the complexities and helps us bypass the smbclient binary to connect to the remote samba server giving us more flexibility in the python tests. Signed-off-by: Sachin Prabhu <[email protected]>
1 parent d1d28f1 commit 0ee7418

File tree

5 files changed

+116
-84
lines changed

5 files changed

+116
-84
lines changed

testcases/consistency/test_consistency.py

Lines changed: 35 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,72 +7,52 @@
77
# ip addresses).
88

99
import testhelper
10+
from testhelper import SMBClient
1011
import os
1112
import pytest
1213
import typing
13-
from pathlib import Path
14-
1514

1615
test_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
1716
test_info_file = os.getenv("TEST_INFO_FILE")
1817
test_info = testhelper.read_yaml(test_info_file)
1918

2019

21-
def file_content_check(f: typing.IO, comp_str: str) -> bool:
22-
read_data = f.read()
23-
return read_data == comp_str
24-
25-
26-
def consistency_check(mount_point: Path, ipaddr: str, share_name: str) -> None:
20+
def consistency_check(hostname: str, share_name: str) -> None:
2721
mount_params = testhelper.get_mount_parameters(test_info, share_name)
28-
mount_params["host"] = ipaddr
29-
try:
30-
test_file = testhelper.get_tmp_file(mount_point)
31-
test_file_resp = test_file.with_suffix(".resp")
32-
test_file_remote = "test-" + ipaddr + "." + share_name
33-
with open(test_file, "w") as f:
34-
f.write(test_string)
35-
put_cmds = "put %s %s" % (test_file, test_file_remote)
36-
(ret, output) = testhelper.smbclient(mount_params, put_cmds)
37-
assert ret == 0, "Failed to copy file to server"
38-
39-
# The file read cycle
40-
get_cmds = "get %s %s; rm %s" % (
41-
test_file_remote,
42-
test_file_resp,
43-
test_file_remote,
44-
)
45-
(ret, output) = testhelper.smbclient(mount_params, get_cmds)
46-
assert ret == 0, "Failed to copy file from server"
47-
with open(test_file_resp, "r") as f:
48-
assert file_content_check(
49-
f, test_string
50-
), "File content does not match"
51-
finally:
52-
if test_file.exists():
53-
test_file.unlink()
54-
if test_file_resp.exists():
55-
test_file_resp.unlink()
56-
57-
58-
def generate_consistency_check(
59-
test_info_file: dict,
60-
) -> typing.List[typing.Tuple[str, str]]:
61-
if not test_info_file:
62-
return []
22+
test_filename = "/test_consistency"
23+
24+
# file write cycle
25+
smbclient = SMBClient(
26+
mount_params["host"],
27+
mount_params["share"],
28+
mount_params["username"],
29+
mount_params["password"],
30+
)
31+
smbclient.write_text(test_filename, test_string)
32+
smbclient.disconnect()
33+
34+
# file read cycle
35+
smbclient = SMBClient(
36+
mount_params["host"],
37+
mount_params["share"],
38+
mount_params["username"],
39+
mount_params["password"],
40+
)
41+
retstr = smbclient.read_text(test_filename)
42+
smbclient.unlink(test_filename)
43+
smbclient.disconnect()
44+
45+
assert retstr == test_string, "File content does not match"
46+
47+
48+
def generate_consistency_check() -> typing.List[typing.Tuple[str, str]]:
6349
arr = []
64-
for share in testhelper.get_exported_shares(test_info):
65-
s = testhelper.get_share(test_info, share)
66-
arr.append((s["server"], s["name"]))
50+
for sharename in testhelper.get_exported_shares(test_info):
51+
share = testhelper.get_share(test_info, sharename)
52+
arr.append((share["server"], share["name"]))
6753
return arr
6854

6955

70-
@pytest.mark.parametrize(
71-
"ipaddr,share_name", generate_consistency_check(test_info)
72-
)
73-
def test_consistency(ipaddr: str, share_name: str) -> None:
74-
tmp_root = testhelper.get_tmp_root()
75-
mount_point = testhelper.get_tmp_mount_point(tmp_root)
76-
consistency_check(mount_point, ipaddr, share_name)
77-
os.rmdir(mount_point)
78-
os.rmdir(tmp_root)
56+
@pytest.mark.parametrize("hostname,share_name", generate_consistency_check())
57+
def test_consistency(hostname: str, share_name: str) -> None:
58+
consistency_check(hostname, share_name)

testhelper/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .testhelper import * # noqa: F401, F403
22
from .cmdhelper import * # noqa: F401, F403
33
from .fshelper import * # noqa: F401, F403
4+
from .smbclient import * # noqa: F401, F403

testhelper/cmdhelper.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,35 +63,6 @@ def cifs_umount(mount_point: Path) -> int:
6363
return ret
6464

6565

66-
def smbclient(
67-
mount_params: typing.Dict[str, str], cmds: str
68-
) -> typing.Tuple[int, str]:
69-
"""Run the following command on smbclient and return the output.
70-
71-
Parameters:
72-
mount_params: Dict containing the mount parameters.
73-
cmds: String containg the ';' separated commands to run.
74-
75-
Returns:
76-
int: Return value from the shell execution
77-
string: stdout
78-
"""
79-
smbclient_cmd = [
80-
"smbclient",
81-
"--user=%s%%%s" % (mount_params["username"], mount_params["password"]),
82-
"//%s/%s" % (mount_params["host"], mount_params["share"]),
83-
"-c",
84-
cmds,
85-
]
86-
ret = subprocess.run(
87-
smbclient_cmd,
88-
universal_newlines=True,
89-
stdout=subprocess.PIPE,
90-
stderr=subprocess.STDOUT,
91-
)
92-
return (ret.returncode, ret.stdout)
93-
94-
9566
def check_cmds(cmds: typing.List[str]) -> Path:
9667
"""Return first file path which exists.
9768

testhelper/smbclient.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from smb.SMBConnection import SMBConnection # type: ignore
2+
from smb import smb_structs, base # type: ignore
3+
import typing
4+
import io
5+
6+
7+
class SMBClient:
8+
"""Use pysmb to access the SMB server"""
9+
10+
def __init__(self, hostname: str, share: str, username: str, passwd: str):
11+
self.server = hostname
12+
self.share = share
13+
self.username = username
14+
self.password = passwd
15+
self.connected = False
16+
self.connect()
17+
18+
def connect(self) -> None:
19+
if self.connected:
20+
return
21+
try:
22+
self.ctx = SMBConnection(
23+
self.username,
24+
self.password,
25+
"smbclient",
26+
self.server,
27+
use_ntlm_v2=True,
28+
)
29+
self.ctx.connect(self.server)
30+
self.connected = True
31+
except base.SMBTimeout as error:
32+
raise IOError(f"failed to connect: {error}")
33+
34+
def disconnect(self) -> None:
35+
self.connected = False
36+
self.ctx.close()
37+
38+
def listdir(self, path: str = "/") -> typing.List[str]:
39+
try:
40+
dentries = self.ctx.listPath(self.share, path)
41+
except smb_structs.OperationFailure as error:
42+
raise IOError(f"failed to readdir: {error}")
43+
return [dent.filename for dent in dentries]
44+
45+
def mkdir(self, dpath: str) -> None:
46+
try:
47+
self.ctx.createDirectory(self.share, dpath)
48+
except smb_structs.OperationFailure as error:
49+
raise IOError(f"failed to mkdir: {error}")
50+
51+
def rmdir(self, dpath: str) -> None:
52+
try:
53+
self.ctx.deleteDirectory(self.share, dpath)
54+
except smb_structs.OperationFailure as error:
55+
raise IOError(f"failed to rmdir: {error}")
56+
57+
def unlink(self, fpath: str) -> None:
58+
try:
59+
self.ctx.deleteFiles(self.share, fpath)
60+
except smb_structs.OperationFailure as error:
61+
raise IOError(f"failed to unlink: {error}")
62+
63+
def write_text(self, fpath: str, teststr: str) -> None:
64+
try:
65+
with io.BytesIO(teststr.encode()) as writeobj:
66+
self.ctx.storeFile(self.share, fpath, writeobj)
67+
except smb_structs.OperationFailure as error:
68+
raise IOError(f"failed in write_text: {error}")
69+
70+
def read_text(self, fpath: str) -> str:
71+
try:
72+
with io.BytesIO() as readobj:
73+
self.ctx.retrieveFile(self.share, fpath, readobj)
74+
ret = readobj.getvalue().decode("utf8")
75+
except smb_structs.OperationFailure as error:
76+
raise IOError(f"failed in read_text: {error}")
77+
return ret

tox.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ deps =
1313
pyyaml
1414
pytest-randomly
1515
iso8601
16+
pysmb
1617
commands = pytest -vrfEsxXpP testcases/
1718

1819
[testenv:pytest-unprivileged]
@@ -21,6 +22,7 @@ deps =
2122
pyyaml
2223
pytest-randomly
2324
iso8601
25+
pysmb
2426
commands = pytest -vrfEsxXpP -k 'not privileged' testcases/
2527

2628
[testenv:sanity]
@@ -29,6 +31,7 @@ deps =
2931
pyyaml
3032
pytest-randomly
3133
iso8601
34+
pysmb
3235
changedir = {toxinidir}
3336
commands = pytest -vrfEsxXpP testcases/consistency
3437

0 commit comments

Comments
 (0)