Skip to content

Commit 1f978b5

Browse files
authored
Merge pull request #164 from jpavlav/jpavlav/optional_get_root_object
Issue #163: Optional Connection upon Client Instantiation
2 parents 7c7df89 + 72a23d7 commit 1f978b5

File tree

3 files changed

+82
-31
lines changed

3 files changed

+82
-31
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ There are several optional parameters:
9191
* ``timeout``: The number of seconds to wait for a response before closing the connection. The default value is ``None``.
9292
* ``max_retry``: The number of retries to perform an operation before giving up. The default value is ``10``.
9393
* ``proxies``: A dictionary containing protocol to proxy URL mappings. The default value is ``None``. See `Using proxies`_.
94+
* ``check_connectivity``: A boolean value to determine whether the client immediately attempts a connection to the base_url. The default is ``True``.
9495

9596
To create a Redfish object, call the ``redfish_client`` method:
9697

src/redfish/rest/v1.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ class RestClientBase(object):
454454
def __init__(self, base_url, username=None, password=None,
455455
default_prefix='/redfish/v1/', sessionkey=None,
456456
capath=None, cafile=None, timeout=None,
457-
max_retry=None, proxies=None):
457+
max_retry=None, proxies=None, check_connectivity=True):
458458
"""Initialization of the base class RestClientBase
459459
460460
:param base_url: The URL of the remote system
@@ -477,6 +477,9 @@ def __init__(self, base_url, username=None, password=None,
477477
:type max_retry: int
478478
:param proxies: Dictionary containing protocol to proxy URL mappings
479479
:type proxies: dict
480+
:param check_connectivity: A boolean to determine whether the client immediately checks for
481+
connectivity to the base_url or not.
482+
:type check_connectivity: bool
480483
481484
"""
482485

@@ -497,7 +500,9 @@ def __init__(self, base_url, username=None, password=None,
497500
self.default_prefix = default_prefix
498501
self.capath = capath
499502
self.cafile = cafile
500-
self.get_root_object()
503+
504+
if check_connectivity:
505+
self.get_root_object()
501506

502507
def __enter__(self):
503508
self.login()
@@ -965,6 +970,8 @@ def login(self, username=None, password=None, auth=AuthMethod.SESSION):
965970
:type auth: object/instance of class AuthMethod
966971
967972
"""
973+
if getattr(self, "root_resp", None) is None:
974+
self.get_root_object()
968975

969976
self.__username = username if username else self.__username
970977
self.__password = password if password else self.__password
@@ -999,7 +1006,7 @@ def login(self, username=None, password=None, auth=AuthMethod.SESSION):
9991006
message_item = search_message(resp, "Base", "PasswordChangeRequired")
10001007
if not message_item is None:
10011008
raise RedfishPasswordChangeRequiredError("Password Change Required\n", message_item["MessageArgs"][0])
1002-
1009+
10031010
if not self.__session_key and resp.status not in [200, 201, 202, 204]:
10041011
if resp.status == 401:
10051012
# Invalid credentials supplied
@@ -1042,7 +1049,7 @@ def __init__(self, base_url, username=None, password=None,
10421049
default_prefix='/redfish/v1/',
10431050
sessionkey=None, capath=None,
10441051
cafile=None, timeout=None,
1045-
max_retry=None, proxies=None):
1052+
max_retry=None, proxies=None, check_connectivity=True):
10461053
"""Initialize HttpClient
10471054
10481055
:param base_url: The url of the remote system
@@ -1065,17 +1072,21 @@ def __init__(self, base_url, username=None, password=None,
10651072
:type max_retry: int
10661073
:param proxies: Dictionary containing protocol to proxy URL mappings
10671074
:type proxies: dict
1075+
:param check_connectivity: A boolean to determine whether the client immediately checks for
1076+
connectivity to the base_url or not.
1077+
:type check_connectivity: bool
10681078
10691079
"""
10701080
super(HttpClient, self).__init__(base_url, username=username,
10711081
password=password, default_prefix=default_prefix,
10721082
sessionkey=sessionkey, capath=capath,
10731083
cafile=cafile, timeout=timeout,
1074-
max_retry=max_retry, proxies=proxies)
1084+
max_retry=max_retry, proxies=proxies,
1085+
check_connectivity=check_connectivity)
10751086

10761087
try:
10771088
self.login_url = self.root.Links.Sessions['@odata.id']
1078-
except KeyError:
1089+
except (KeyError, AttributeError):
10791090
# While the "Links/Sessions" property is required, we can fallback
10801091
# on the URI hardened in 1.6.0 of the specification if not found
10811092
LOGGER.debug('"Links/Sessions" not found in Service Root.')
@@ -1128,7 +1139,7 @@ def redfish_client(base_url=None, username=None, password=None,
11281139
default_prefix='/redfish/v1/',
11291140
sessionkey=None, capath=None,
11301141
cafile=None, timeout=None,
1131-
max_retry=None, proxies=None):
1142+
max_retry=None, proxies=None, check_connectivity=True):
11321143
"""Create and return appropriate REDFISH client instance."""
11331144
""" Instantiates appropriate Redfish object based on existing"""
11341145
""" configuration. Use this to retrieve a pre-configured Redfish object
@@ -1153,6 +1164,9 @@ def redfish_client(base_url=None, username=None, password=None,
11531164
:type max_retry: int
11541165
:param proxies: Dictionary containing protocol to proxy URL mappings
11551166
:type proxies: dict
1167+
:param check_connectivity: A boolean to determine whether the client immediately checks for
1168+
connectivity to the base_url or not.
1169+
:type check_connectivity: bool
11561170
:returns: a client object.
11571171
11581172
"""
@@ -1162,4 +1176,4 @@ def redfish_client(base_url=None, username=None, password=None,
11621176
return HttpClient(base_url=base_url, username=username, password=password,
11631177
default_prefix=default_prefix, sessionkey=sessionkey,
11641178
capath=capath, cafile=cafile, timeout=timeout,
1165-
max_retry=max_retry, proxies=proxies)
1179+
max_retry=max_retry, proxies=proxies, check_connectivity=check_connectivity)

tests/rest/test_v1.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,76 @@
44
# https://github.com/DMTF/python-redfish-library/blob/main/LICENSE.md
55

66
# -*- encoding: utf-8 -*-
7+
import json
78
import unittest
9+
from unittest import mock
810

9-
from redfish.rest.v1 import HttpClient
10-
from redfish.rest.v1 import RetriesExhaustedError
11-
from redfish.rest.v1 import redfish_client
11+
from redfish.rest.v1 import HttpClient, RetriesExhaustedError, redfish_client
1212

1313

1414
class TestRedFishClient(unittest.TestCase):
15-
def test_redfish_client(self):
16-
base_url = "http://foo.bar"
17-
username = "rstallman"
18-
password = "123456"
19-
default_prefix = "/custom/redfish/v1/"
20-
sessionkey = "fg687glgkf56vlgkf"
21-
capath = "/path/to/the/dir"
22-
cafile = "filename.test"
23-
timeout = 666
24-
max_retry = 42
15+
def setUp(self) -> None:
16+
self.base_url = "http://foo.bar"
17+
self.username = "rstallman"
18+
self.password = "123456"
19+
self.default_prefix = "/custom/redfish/v1/"
20+
self.sessionkey = "fg687glgkf56vlgkf"
21+
self.capath = "/path/to/the/dir"
22+
self.cafile = "filename.test"
23+
self.timeout = 666
24+
self.max_retry = 42
25+
26+
def test_redfish_client(self) -> None:
2527
# NOTE(hberaud) the client try to connect when we initialize the
2628
# http client object so we need to catch the retries exception first.
2729
# In a second time we need to mock the six.http_client to simulate
2830
# server responses and do some other tests
2931
with self.assertRaises(RetriesExhaustedError):
30-
client = redfish_client(base_url=base_url)
32+
client = redfish_client(base_url=self.base_url)
3133
# Check the object type
3234
self.assertTrue(isinstance(client, HttpClient))
3335
# Check the object attributes values.
3436
# Here we check if the client object is properly initialized
35-
self.assertEqual(client.base_url, base_url)
36-
self.assertEqual(client.username, username)
37-
self.assertEqual(client.password, password)
38-
self.assertEqual(client.default_prefix, default_prefix)
39-
self.assertEqual(client.sessionkey, sessionkey)
40-
self.assertEqual(client.capath, capath)
41-
self.assertEqual(client.cafile, cafile)
42-
self.assertEqual(client.timeout, timeout)
43-
self.assertEqual(client.max_retry, max_retry)
37+
self.assertEqual(client.base_url, self.base_url)
38+
self.assertEqual(client.username, self.username)
39+
self.assertEqual(client.password, self.password)
40+
self.assertEqual(client.default_prefix, self.default_prefix)
41+
self.assertEqual(client.sessionkey, self.sessionkey)
42+
self.assertEqual(client.capath, self.capath)
43+
self.assertEqual(client.cafile, self.cafile)
44+
self.assertEqual(client.timeout, self.timeout)
45+
self.assertEqual(client.max_retry, self.max_retry)
46+
47+
def test_redfish_client_no_root_resp(self) -> None:
48+
client = redfish_client(base_url=self.base_url, check_connectivity=False)
49+
self.assertIsNone(getattr(client, "root_resp", None))
50+
51+
@mock.patch("requests.Session.request")
52+
def test_redfish_client_root_object_initialized_after_login(
53+
self, mocked_request: mock.Mock
54+
) -> None:
55+
dummy_root_data = '{"Links": {"Sessions": {"@data.id": "/redfish/v1/SessionService/Sessions"}}}'
56+
dummy_session_response = (
57+
'{"@odata.type": "#Session.v1_1_2.Session", '
58+
'"@odata.id": "/redfish/v1/SessionService/Sessions/1", '
59+
'"Id": "1", "Name": "User Session", "Description": "Manager User Session", '
60+
'"UserName": "user", "Oem": {}}'
61+
)
62+
root_resp = mock.Mock(content=dummy_root_data, status_code=200)
63+
auth_resp = mock.Mock(
64+
content=dummy_session_response,
65+
status_code=200,
66+
headers={"location": "fake", "x-auth-token": "fake"},
67+
)
68+
mocked_request.side_effect = [
69+
root_resp,
70+
auth_resp,
71+
]
72+
client = redfish_client(base_url=self.base_url, check_connectivity=False)
73+
client.login()
74+
75+
self.assertEqual(client.root, json.loads(dummy_root_data))
76+
77+
78+
if __name__ == "__main__":
79+
unittest.main()

0 commit comments

Comments
 (0)