DMTF/python-redfish-library

AttributeError: module 'redfish' has no attribute 'redfish_client'

chhuang789 opened this issue · 35 comments

Dear SIr:
I got an error said the module 'redfish' has no attribute 'redfish_client' and below is my env. Is there something wrong of my env?

python -V
Python 3.9.4

cat quickstart.py

# Copyright Notice:
# Copyright 2016-2021 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link:
# https://github.com/DMTF/python-redfish-library/blob/master/LICENSE.md

import sys
import redfish

# When running remotely connect using the address, account name,
# and password to send https requests
login_host = "https://172.17.21.120"
login_account = "admin"
login_password = "password"

## Create a REDFISH object
REDFISH_OBJ = redfish.redfish_client(base_url=login_host, username=login_account,
                          password=login_password, default_prefix='/redfish/v1')

# Login into the server and create a session
REDFISH_OBJ.login(auth="session")

# Do a GET on a given path
response = REDFISH_OBJ.get("/redfish/v1/systems/1", None)

# Print out the response
sys.stdout.write("%s\n" % response)

# Logout of the current session
REDFISH_OBJ.logout()

Result:
python quickstart.py
Traceback (most recent call last):
File "/Users/ch.huang789/Python/python-redfish-library/examples/quickstart.py", line 16, in
REDFISH_OBJ = redfish.redfish_client(base_url=login_host, username=login_account,
AttributeError: module 'redfish' has no attribute 'redfish_client'

Can you please provide the output of the following: pip show redfish

Hi Mraineri:

pip show redfish
Name: redfish
Version: 3.0.0
Summary: Redfish Python Library
Home-page: https://github.com/DMTF/python-redfish-library
Author: DMTF, https://www.dmtf.org/standards/feedback
Author-email: None
License: BSD 3-clause "New" or "Revised License"
Location: /Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages
Requires: jsonpointer, jsonpatch, jsonpath-rw
Required-by:

There could be something odd about your environment; usually when I see a module installed on a system, it's installed for all users, but I see yours is in a local user directory.

Would you be able to add the following code after import redfish in the quickstart file and provide the output? This should tell us where the module is being loaded from.

import os
path = os.path.abspath(redfish.__file__)
print(path)

Dear Sir:

Here are the output:
/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/init.py

Thanks.
CH

Thanks for the info. This is definitely strange to me; the file path looks correct. I've been trying to reproduce this with a fresh Python3.9 installation on my system and cannot reproduce this. I'll continue to think of next steps to help debug this further.

Ok, thanks in advance.

@chhuang789 one thing that I just noticed is the init file isn't named the same as what's been released (you have init.py but my system and others have __init__.py. Can you show me the output of:
ls -alR /Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish

I'd like to see if you're getting the same file structure as what we distribute.

Hi Sir:

Here is the output:

ls -alR /Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish

total 136
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 .
drwxr-xr-x 248 ch.huang789 staff 7936 5 23 10:13 ..
-rw-r--r-- 1 ch.huang789 staff 1087 5 18 21:10 init.py
drwxr-xr-x 9 ch.huang789 staff 288 5 18 21:10 pycache
-rw-r--r-- 1 ch.huang789 staff 2750 5 18 21:10 config.py
drwxr-xr-x 5 ch.huang789 staff 160 5 18 20:37 discovery
-rw-r--r-- 1 ch.huang789 staff 2072 5 18 21:10 exception.py
-rw-r--r-- 1 ch.huang789 staff 16333 5 18 21:10 main.py
-rw-r--r-- 1 ch.huang789 staff 1839 5 18 21:10 mapping.py
drwxr-xr-x 5 ch.huang789 staff 160 5 18 21:10 oem
drwxr-xr-x 5 ch.huang789 staff 160 5 18 20:37 rest
drwxr-xr-x 9 ch.huang789 staff 288 5 18 20:37 ris
-rw-r--r-- 1 ch.huang789 staff 20932 5 18 21:10 standard.py
drwxr-xr-x 4 ch.huang789 staff 128 5 18 21:10 tests
-rw-r--r-- 1 ch.huang789 staff 11877 5 18 21:10 types.py

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/pycache:
total 120
drwxr-xr-x 9 ch.huang789 staff 288 5 18 21:10 .
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 716 5 18 21:10 init.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 2352 5 18 21:10 config.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 2730 5 18 21:10 exception.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 11695 5 18 21:10 main.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 1888 5 18 21:10 mapping.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 17984 5 18 21:10 standard.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 10808 5 18 21:10 types.cpython-39.pyc

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/discovery:
total 16
drwxr-xr-x 5 ch.huang789 staff 160 5 18 20:37 .
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 202 5 18 20:37 init.py
drwxr-xr-x 4 ch.huang789 staff 128 5 18 20:37 pycache
-rw-r--r-- 1 ch.huang789 staff 4078 5 18 20:37 discovery.py

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/discovery/pycache:
total 16
drwxr-xr-x 4 ch.huang789 staff 128 5 18 20:37 .
drwxr-xr-x 5 ch.huang789 staff 160 5 18 20:37 ..
-rw-r--r-- 1 ch.huang789 staff 189 5 18 20:37 init.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 3485 5 18 20:37 discovery.cpython-39.pyc

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/oem:
total 24
drwxr-xr-x 5 ch.huang789 staff 160 5 18 21:10 .
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 570 5 18 21:10 init.py
drwxr-xr-x 4 ch.huang789 staff 128 5 18 21:10 pycache
-rw-r--r-- 1 ch.huang789 staff 6341 5 18 21:10 hpe.py

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/oem/pycache:
total 24
drwxr-xr-x 4 ch.huang789 staff 128 5 18 21:10 .
drwxr-xr-x 5 ch.huang789 staff 160 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 183 5 18 21:10 init.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 6431 5 18 21:10 hpe.cpython-39.pyc

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/rest:
total 88
drwxr-xr-x 5 ch.huang789 staff 160 5 18 20:37 .
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 265 5 18 20:37 init.py
drwxr-xr-x 4 ch.huang789 staff 128 5 18 20:37 pycache
-rw-r--r-- 1 ch.huang789 staff 40257 5 18 20:37 v1.py

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/rest/pycache:
total 72
drwxr-xr-x 4 ch.huang789 staff 128 5 18 20:37 .
drwxr-xr-x 5 ch.huang789 staff 160 5 18 20:37 ..
-rw-r--r-- 1 ch.huang789 staff 249 5 18 20:37 init.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 32577 5 18 20:37 v1.cpython-39.pyc

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/ris:
total 264
drwxr-xr-x 9 ch.huang789 staff 288 5 18 20:37 .
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 775 5 18 20:37 init.py
drwxr-xr-x 8 ch.huang789 staff 256 5 18 20:37 pycache
-rw-r--r-- 1 ch.huang789 staff 4480 5 18 20:37 config.py
-rw-r--r-- 1 ch.huang789 staff 21139 5 18 20:37 ris.py
-rw-r--r-- 1 ch.huang789 staff 67342 5 18 20:37 rmc.py
-rw-r--r-- 1 ch.huang789 staff 23472 5 18 20:37 rmc_helper.py
-rw-r--r-- 1 ch.huang789 staff 1275 5 18 20:37 sharedtypes.py

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/ris/pycache:
total 184
drwxr-xr-x 8 ch.huang789 staff 256 5 18 20:37 .
drwxr-xr-x 9 ch.huang789 staff 288 5 18 20:37 ..
-rw-r--r-- 1 ch.huang789 staff 774 5 18 20:37 init.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 3308 5 18 20:37 config.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 16211 5 18 20:37 ris.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 39681 5 18 20:37 rmc.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 22655 5 18 20:37 rmc_helper.cpython-39.pyc
-rw-r--r-- 1 ch.huang789 staff 1341 5 18 20:37 sharedtypes.cpython-39.pyc

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/tests:
total 8
drwxr-xr-x 4 ch.huang789 staff 128 5 18 21:10 .
drwxr-xr-x 15 ch.huang789 staff 480 5 18 21:10 ..
drwxr-xr-x 3 ch.huang789 staff 96 5 18 21:10 pycache
-rw-r--r-- 1 ch.huang789 staff 308 5 18 21:10 test_redfish.py

/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/tests/pycache:
total 8
drwxr-xr-x 3 ch.huang789 staff 96 5 18 21:10 .
drwxr-xr-x 4 ch.huang789 staff 128 5 18 21:10 ..
-rw-r--r-- 1 ch.huang789 staff 576 5 18 21:10 test_redfish.cpython-39.pyc

It looks like you have a very old Redfish library that was originally developed by OpenStack and is currently defunct. I know if it's not removed prior to installing the current package, you can hit conflicts.

Can you run the following to clean the packages?

pip uninstall python-redfish
pip uninstall redfish
pip install redfish

Hi Sir:

Thanks.
It looks better and successful pass the redfish.redfish_client

But failed during the login. (REDFISH_OBJ.login(auth="session") as below error output:

python quickstart.py
/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/init.py
Traceback (most recent call last):
File "/Users/ch.huang789/Python/python-redfish-library/examples/quickstart.py", line 23, in
REDFISH_OBJ.login(auth="session")
File "/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/site-packages/redfish/rest/v1.py", line 1001, in login
LOGGER.info(json.loads('%s' % resp.text))
File "/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/json/init.py", line 346, in loads
return _default_decoder.decode(s)
File "/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/Users/ch.huang789/.pyenv/versions/3.9.4/lib/python3.9/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

In cases like that it means the service responded with an invalid JSON response. From where it took the exception, it's the response payload from the POST to the session collection. The expectation is the response to that will be a JSON payload that contains the representation of the new session.

Ok, Sir.

Let me check with the server vendor. Thanks very much.
BTW, I put the output of the /redfish/v1 for your reference.

https://172.17.21.120/redfish/v1 (No need to login in for default prefix="/redfish/v1")

----> result as:
{
@odata.type: "#ServiceRoot.v1_0_0.ServiceRoot",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.ServiceRoot",
@odata.id: "/redfish/v1",
@odata.etag: "W/"3f1cf64f3c66670a"",
Id: "RootService",
Name: "Root Service",
RedfishVersion: "1.3.1",
OData: {
@odata.type: "#OData.v1_0_0.OData",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.OData",
@odata.id: "/redfish/v1/odata",
@odata.etag: "W/"3f1cf64f3c66670a""
},
SessionService: {
@odata.type: "#SessionService.v1_0_0.SessionService",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.SessionService",
@odata.id: "/redfish/v1/SessionService",
@odata.etag: "W/"3f1cf64f3c66670a""
},
AccountService: {
@odata.type: "#AccountService.v1_1_0.AccountService",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.AccountService",
@odata.id: "/redfish/v1/AccountService",
@odata.etag: "W/"3f1cf64f3c66670a""
},
EventService: {
@odata.type: "#EventService.v1_0_0.EventService",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.EventService",
@odata.id: "/redfish/v1/EventService",
@odata.etag: "W/"3f1cf64f3c66670a""
},
Systems: {
@odata.type: "#ComputerSystemCollection.ComputerSystemCollection",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.Systems",
@odata.id: "/redfish/v1/Systems",
@odata.etag: "W/"3f1cf64f3c66670a""
},
Chassis: {
@odata.type: "#ChassisCollection.ChassisCollection",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.Chassis",
@odata.id: "/redfish/v1/Chassis",
@odata.etag: "W/"3f1cf64f3c66670a""
},
Managers: {
@odata.type: "#ManagerCollection.ManagerCollection",
@odata.context: "/redfish/v1/$metadata/ServiceRoot.Managers",
@odata.id: "/redfish/v1/Managers",
@odata.etag: "W/"3f1cf64f3c66670a""
},
Links: {
Sessions: {
@odata.id: "/redfish/v1/SessionService/Sessions"
}
}
}

At least from that output of service root, there are numerous things not correct with the response. It is valid JSON at least, and the things not correct are purely additive so it shouldn't be detrimental to clients. For example, I would not expect to see anything other than @odata.id in properties like Chassis, Managers, Systems, etc.

One other thing that might be of use for debug is to see what the response to the POST to the session collection is when using curl.

curl -k -D - -X POST 'https://172.17.21.120/redfish/v1/SessionService/Sessions' -H "Content-Type: application/json" -d '{ "UserName": "admin", "Password": "password" }'

Hi Sir:

  1. I saw a 302 forward. Does it matter?
  2. There still have @odata.xxx in the response.

curl -k -D - -X POST 'https://172.17.21.120/redfish/v1/SessionService/Sessions' -H "Content-Type: application/json" -d '{ "UserName": "administrator", "Password": "advantech" }'
HTTP/1.1 302 Found
OData-Version: 4.0
X-Auth-Token: 7e9ea6a0a8adb52394e23027839d788c
Location: https://172.17.21.120/redfish/v1/SessionService/Sessions/f50de87ab281af5d3560f9c3b979bcdf
Etag: W/"84efa566a7c3814c"
WWW-Authenticate: Basic realm="administrator"
Allow: GET,POST
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Content-Type: application/json
Strict-Transport-Security: max-age=6307200; includeSubdomains;
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
Content-Length: 372
Date: Thu, 27 May 2021 00:51:24 GMT
Server: lighttpd

{
"@odata.type": "#Session.v1_0_0.Session",
"@odata.context": "/redfish/v1/$metadata/Session.Session",
"@odata.id": "/redfish/v1/SessionService/Sessions/f50de87ab281af5d3560f9c3b979bcdf",
"@odata.etag": "W/"84efa566a7c3814c"",
"Id": "f50de87ab281af5d3560f9c3b979bcdf",
"Name": "User Session",
"Description": "Manager User Session",
"UserName": "administrator"
}

The redirect might matter here; I need to see how redirections are managed by the library.

I would expect @odata.type in this response; @odata.type is used in the root of responses to convey the payload format.

It looks like the redirect shouldn't matter; there is generalized redirect handling in place.

Looking at the payload, it actually is invalid JSON. The @odata.etag property in the response isn't escaping the " characters that are part of the value of the property. It should be "@odata.etag": "W/\"84efa566a7c3814c\"" (note the two additional \ characters for escaping).

I also just realized the service root you pasted into this issue is invalid JSON; it's missing double quotes around the property names and also has the same missing escape slashes in the ETag properties.

Hi Sri:

I am sorry, when i copy the result to this text body. It will re-format my output. Please check the screen capture.
image

And back to the default prefix (/redfish/v1), the screen capture of the raw data is:
image

Thanks; I'll need to take another look at how the redirection is being handled (in particular what it means in response to a POST).

Would you be able to add -v to the curl command and send me the output? I'm hoping the verbosity increase will show what curl is doing with the redirection and hopefully I can line that up with what's happening in the library.

Hi Sir:

Sure,
image
image
image

Thanks; unfortunately the verbose output doesn't show the redirect handling as far as I can tell, but upon digging more into curl and some of the RFCs that document the redirect status codes, it seems like the library is not following the guidance of handling the 302 redirection to perform a GET instead of another POST. The library does this for 303, but not for 301 and 302; curl has this behavior for 301, 302, and 303. I'll be making some changes to the library to address this.

@chhuang789 I created PR #108 to address this; would you be able to test it for me with your system?

Hi Sir:

I change the code as
## if resp.status == 303:
print(resp.status)
print(resp.msg)
if resp.status == 301 or resp.status == 302 or resp.status == 303:

And then test it with the result as below screenshot or attached log file:
image
20210529_135102.Default.w0t0p0.029D00F7-C3EF-4AF3-8A85-282C19FD2916.3939.1613665994.log

Hi Sir:

I add one more line to test as below:
method = 'GET'
body = None
headers['X-Auth-Token'] = resp.getheader('X-Auth-Token') // This the line that I added
for h in ['Content-Type', 'Content-Length']:

And the see the http response 200. But I don't think this is a fix. Just for your reference. Still need your help.
20210529_180422.Default.w0t0p0.24FF3067-9926-466F-9818-13EBA7CD594C.5874.4251837171.log

Hi Sir:

If you want me to expose the IP on the internet. I can arrange someone to do it. But it may take time because I am working at home.

Thanks for the offer, but I'd rather not risk putting your equipment on the internet. I'll need to think about some next steps to approach this.

I have some ideas about what's going on; like how you've had to add handling of the X-Auth-Token in the redirection, it appears the Location header is not being stored in this case (perhaps the subsequent GET when following the redirect loses this info). The same exception during logout appears if the cached session location is None. I'll need to think about the right way to manage this case.

I've looked at other samples specifically for how others manage this redirection case; essentially since the headers are critical to managing the session, the redirection for this scenario tends to be avoided.

I've updated the branch based on this; would you be able to test it for me?

Hi Sir:

Sure, I am glad to do the test.

Case 1: TypeError: a bytes-like object is required, not 'str'

I am trying to google it and found one of the add 3 lines before line [reqpath = path.replace('//', '/')]
if type(path) is bytes:
reqpath = path.decode().replace('//', '/')
else:
reqpath = path.replace('//', '/')

It looks works for case 1, but still need your confirm:

Case 2: after changed of case 1. I still get the error code 403 Forbidden. So, I change the code as below:

From:
session_loc = urlparse(self.__session_location).path
to:
session_loc = urlparse(self.login_url).path + '/' + self.__session_key

And the response code become 400.

Then I have no idea how to do for next step. Please help on it.
Thanks very much.

Note: what I did as above is not the fix. Just my workaround for your reference.

logout log as attachment.
logout.log

It's still not saving the session it looks like. The session key and the session URI are two different things, so they need to be tracked differently.

In your original logs there is a Location header returned (which is the session location):

X-Auth-Token: 11d9f0d70c631a3c6e36d41f30eb680a
Location: https://172.17.21.120/redfish/v1/SessionService/Sessions/27e90931f79482e027d94569d9413188

I'll double check what I added to the branch and see how to debug this further.

Nothing obvious from testing on my end why Location is not being extracted and saved. I added some additional tracing of requests/responses to help what's going on. Could you please try out the update I just pushed and provide the output?

Hi Sir:

Now its working well. Please check the log file for your double confirm. Thanks very much.
20210602_223928.Default.w0t0p0.C64BFF19-E920-4405-8FDF-EFB6FE9B5B04.4621.434671540.log

Thanks for confirming! Looks good to me too; I'll back out the debug prints and get this wrapped up for the next release later this week.