Skip to content

Commit ed7b5ad

Browse files
committed
Ignore nones in login for supermicro
1 parent 1b362df commit ed7b5ad

File tree

11 files changed

+399
-277
lines changed

11 files changed

+399
-277
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ htmlcov
77
dist
88
build/
99
Pipfile.lock
10+
.vscode

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ coverage:
1717
xdg-open htmlcov/index.html
1818

1919
lint:
20-
pipenv run ruff redfish_client
20+
pipenv run ruff check redfish_client tests --fix
21+
pipenv run ruff format redfish_client tests
2122

2223
publish:
2324
rm -rf dist/*

redfish_client/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@
1717
from redfish_client.root import Root
1818

1919

20-
def connect(base_url, username, password, verify=True, cache=True,
21-
lazy_load=True, timeout=Connector.DEFAULT_TIMEOUT):
20+
def connect(
21+
base_url,
22+
username,
23+
password,
24+
verify=True,
25+
cache=True,
26+
lazy_load=True,
27+
timeout=Connector.DEFAULT_TIMEOUT,
28+
):
2229
klass = CachingConnector if cache else Connector
2330
connector = klass(base_url, username, password, verify=verify, timeout=timeout)
2431
root = Root(connector, oid="/redfish/v1", lazy=lazy_load)

redfish_client/connector.py

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@
2929
class Connector:
3030
# Default headers, as required by Redfish spec
3131
# https://redfish.dmtf.org/schemas/DSP0266_1.5.0.html#request-headers
32-
DEFAULT_HEADERS = {
33-
"Accept": "application/json",
34-
"OData-Version": "4.0"
35-
}
32+
DEFAULT_HEADERS = {"Accept": "application/json", "OData-Version": "4.0"}
3633
DEFAULT_TIMEOUT = 1 # In seconds
3734

3835
def __init__(self, base_url, username, password, verify=True, timeout=DEFAULT_TIMEOUT):
@@ -55,33 +52,37 @@ def _url(self, path):
5552

5653
def _log_request(self, method, path, payload, headers):
5754
try:
58-
logger.debug(json.dumps(dict(
59-
request=dict(
60-
method=method,
61-
base_url=self._base_url,
62-
path=path,
63-
payload=payload,
64-
headers=headers,
55+
logger.debug(
56+
json.dumps(
57+
dict(
58+
request=dict(
59+
method=method,
60+
base_url=self._base_url,
61+
path=path,
62+
payload=payload,
63+
headers=headers,
64+
)
65+
)
6566
)
66-
)))
67+
)
6768
except Exception as e:
6869
logger.error(e)
6970

7071
def _log_response(self, method, path, response, json_data):
7172
try:
72-
logger.debug(json.dumps(dict(
73-
request_data=dict(
74-
method=method,
75-
base_url=self._base_url,
76-
path=path
77-
),
78-
response=dict(
79-
status_code=response.status_code,
80-
headers=dict(response.headers.lower_items()),
81-
content=str(response.content),
82-
json_data=json_data
73+
logger.debug(
74+
json.dumps(
75+
dict(
76+
request_data=dict(method=method, base_url=self._base_url, path=path),
77+
response=dict(
78+
status_code=response.status_code,
79+
headers=dict(response.headers.lower_items()),
80+
content=str(response.content),
81+
json_data=json_data,
82+
),
83+
)
8384
)
84-
)))
85+
)
8586
except Exception as e:
8687
logger.error(e)
8788

@@ -94,25 +95,16 @@ def _request(self, method, path, payload=None, headers=None, data=None, files=No
9495
args["files"] = files
9596
try:
9697
resp = self._client.request(
97-
method,
98-
self._url(path),
99-
**args,
100-
headers=headers,
101-
timeout=self._timeout
98+
method, self._url(path), **args, headers=headers, timeout=self._timeout
10299
)
103100
except requests.exceptions.ConnectionError:
104-
raise InaccessibleException(
105-
"Endpoint at {} is not accessible".format(self._base_url))
101+
raise InaccessibleException("Endpoint at {} is not accessible".format(self._base_url))
106102

107103
if resp.status_code == 401:
108104
self._unset_header("x-auth-token")
109105
self.login()
110106
resp = self._client.request(
111-
method,
112-
self._url(path),
113-
**args,
114-
headers=headers,
115-
timeout=self._timeout
107+
method, self._url(path), **args, headers=headers, timeout=self._timeout
116108
)
117109

118110
try:
@@ -159,9 +151,14 @@ def _has_session_support(self):
159151
return bool(self._session_path)
160152

161153
def _session_login(self):
162-
resp = self._client.post(self._url(self._session_path), json=dict(
163-
UserName=self._username, Password=self._password,
164-
), timeout=self._timeout)
154+
resp = self._client.post(
155+
self._url(self._session_path),
156+
json=dict(
157+
UserName=self._username,
158+
Password=self._password,
159+
),
160+
timeout=self._timeout,
161+
)
165162
if resp.status_code not in [201, 204]:
166163
raise AuthException("Cannot create session: {}".format(resp.text))
167164

@@ -189,12 +186,13 @@ def _session_logout(self):
189186
self._unset_header("x-auth-token")
190187

191188
def _basic_login(self):
192-
secret = "Basic {}".format(base64.b64encode(
193-
"{}:{}".format(self._username, self._password).encode("ascii"),
194-
).decode("ascii"))
189+
secret = "Basic {}".format(
190+
base64.b64encode(
191+
"{}:{}".format(self._username, self._password).encode("ascii"),
192+
).decode("ascii")
193+
)
195194
resp = self._client.get(
196-
self._url(self._basic_path), headers=dict(authorization=secret),
197-
timeout=self._timeout
195+
self._url(self._basic_path), headers=dict(authorization=secret), timeout=self._timeout
198196
)
199197
if resp.status_code != 200:
200198
raise AuthException("Invalid credentials")

redfish_client/resource.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@ def _build(self, data):
8282

8383
def _build_from_hash(self, data):
8484
if "@odata.id" in data:
85-
return Resource(
86-
self._connector, oid=data["@odata.id"], lazy=self._is_lazy
87-
)
85+
return Resource(self._connector, oid=data["@odata.id"], lazy=self._is_lazy)
8886
return Resource(self._connector, data=data, lazy=self._is_lazy)
8987

9088
def refresh(self):
@@ -134,7 +132,7 @@ def jp(self):
134132
return json.dumps(self.raw, indent=4, sort_keys=True)
135133

136134
def find_object(self, key):
137-
""" Recursively search for a key and return key's content """
135+
"""Recursively search for a key and return key's content"""
138136
if key in self._get_content().keys():
139137
return self[key]
140138

@@ -160,8 +158,12 @@ def execute_action(self, action_name, payload):
160158
raise KeyError("Action with {} does not exist".format(action_name))
161159

162160
def wait_for(
163-
self, stat, expected, blacklisted=None, poll_interval=3,
164-
timeout=15,
161+
self,
162+
stat,
163+
expected,
164+
blacklisted=None,
165+
poll_interval=3,
166+
timeout=15,
165167
):
166168
"""
167169
:param stat: list or tuple of keys
@@ -191,9 +193,7 @@ def wait_for(
191193
"Detected blacklisted value '{}'".format(actual_value)
192194
)
193195
time.sleep(poll_interval)
194-
raise TimedOutException(
195-
"Could not wait for stat {} in time".format(stat)
196-
)
196+
raise TimedOutException("Could not wait for stat {} in time".format(stat))
197197

198198
@property
199199
def raw(self):

redfish_client/root.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ def login(self):
2020
content = self._get_content()
2121
sessions = content.get("Links", {}).get("Sessions", {})
2222
authenticated_path = next(
23-
i["@odata.id"] for i in content.values() if "@odata.id" in i
23+
# supermicro can send none here which causes this to blow up,
24+
# so we need to check that we actually have a value
25+
i["@odata.id"]
26+
for i in content.values()
27+
if i and "@odata.id" in i
2428
)
2529
if "@odata.id" in sessions:
2630
self._connector.set_session_auth_data(sessions["@odata.id"])

ruff.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
select = [
1+
lint.select = [
22
"B", # bugbear: https://beta.ruff.rs/docs/rules/#flake8-bugbear-b
33
"DJ", # django: https://beta.ruff.rs/docs/rules/#flake8-django-dj
44
"E", # errors: https://beta.ruff.rs/docs/rules/#error-e
@@ -14,7 +14,7 @@ select = [
1414
]
1515
line-length = 100
1616
target-version = "py311"
17-
ignore = [
17+
lint.ignore = [
1818
"PLR2004", # Constants
1919
"B904", # Raising exceptions "from"
2020
"PLR0913", # Too many arguments

tests/test_caching_connector.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@
1818

1919
class TestGet:
2020
def test_get_caching_ok(self, requests_mock):
21-
requests_mock.get("https://demo.dev/data", [
22-
dict(status_code=200, json=dict(hello="fish")),
23-
dict(status_code=200, json=dict(solong="fish")),
24-
])
21+
requests_mock.get(
22+
"https://demo.dev/data",
23+
[
24+
dict(status_code=200, json=dict(hello="fish")),
25+
dict(status_code=200, json=dict(solong="fish")),
26+
],
27+
)
2528
conn = CachingConnector("https://demo.dev", None, None)
2629
assert conn.get("/data").json == dict(hello="fish")
2730
assert conn.get("/data").json == dict(hello="fish")
2831

2932
def test_get_caching_non_ok(self, requests_mock):
30-
requests_mock.get("https://demo.dev/data", [
31-
dict(status_code=404, json=dict(error="bad")),
32-
dict(status_code=500, json=dict(really="bad")),
33-
])
33+
requests_mock.get(
34+
"https://demo.dev/data",
35+
[
36+
dict(status_code=404, json=dict(error="bad")),
37+
dict(status_code=500, json=dict(really="bad")),
38+
],
39+
)
3440
conn = CachingConnector("https://demo.dev", None, None)
3541
assert conn.get("/data").json == dict(error="bad")
3642
assert conn.get("/data").json == dict(really="bad")
@@ -39,14 +45,20 @@ def test_get_caching_non_ok(self, requests_mock):
3945
class TestReset:
4046
@staticmethod
4147
def mock_paths(mock):
42-
mock.get("https://demo.dev/1", [
43-
dict(status_code=200, json=dict(hello="fish")),
44-
dict(status_code=200, json=dict(hello="bear")),
45-
])
46-
mock.get("https://demo.dev/2", [
47-
dict(status_code=200, json=dict(solong="fish")),
48-
dict(status_code=200, json=dict(solong="bear")),
49-
])
48+
mock.get(
49+
"https://demo.dev/1",
50+
[
51+
dict(status_code=200, json=dict(hello="fish")),
52+
dict(status_code=200, json=dict(hello="bear")),
53+
],
54+
)
55+
mock.get(
56+
"https://demo.dev/2",
57+
[
58+
dict(status_code=200, json=dict(solong="fish")),
59+
dict(status_code=200, json=dict(solong="bear")),
60+
],
61+
)
5062

5163
def test_reset_all(self, requests_mock):
5264
self.mock_paths(requests_mock)

tests/test_connect.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from redfish_client import connect
2+
3+
4+
def test_connect(requests_mock):
5+
requests_mock.get(
6+
"https://demo.dev/redfish/v1",
7+
status_code=200,
8+
json={
9+
"Chassis": {"@odata.id": "/redfish/v1/Chassis"},
10+
"Links": {"Sessions": {"@odata.id": "/redfish/v1/SessionService/Sessions"}},
11+
},
12+
)
13+
requests_mock.post(
14+
"https://demo.dev/redfish/v1/SessionService/Sessions",
15+
status_code=201,
16+
headers={"X-Auth-Token": "fake-token", "Location": "fake-location"},
17+
json=dict(hello="fish3"),
18+
)
19+
conn = connect("https://demo.dev/", "user", "pass")
20+
assert conn
21+
22+
23+
def test_connect_nulls(requests_mock):
24+
requests_mock.get(
25+
"https://demo.dev/redfish/v1",
26+
status_code=200,
27+
json={
28+
"foo": None,
29+
"Chassis": {"@odata.id": "/redfish/v1/Chassis"},
30+
"Links": {"Sessions": {"@odata.id": "/redfish/v1/SessionService/Sessions"}},
31+
},
32+
)
33+
requests_mock.post(
34+
"https://demo.dev/redfish/v1/SessionService/Sessions",
35+
status_code=201,
36+
headers={"X-Auth-Token": "fake-token", "Location": "fake-location"},
37+
json=dict(hello="fish3"),
38+
)
39+
conn = connect("https://demo.dev/", "user", "pass")
40+
assert conn

0 commit comments

Comments
 (0)