cubewise-code / tm1py

TM1py is a Python package that wraps the TM1 REST API in a simple to use library.
http://tm1py.readthedocs.io/en/latest/
MIT License
187 stars 107 forks source link

Troubleshooting connection to IBM Cloud instance #298

Closed dltswatts closed 4 years ago

dltswatts commented 4 years ago

TM1py version: 1.4.1 TM1 version: 2.09 (on IBM Cloud)

Following previous threads on how to connect to TM1 on the IBM Cloud, I ran the following connection string with TM1py:

tm1 = TM1Service(base_url='https://_mycloudid_.planning-analytics.ibmcloud.com/tm1/api/_mytm1server_/', user="mynoninteractivID", namespace="LDAP", password="mynoninteractivePW", ssl=True)

I got a 'Status Code: 401 Reason: Unauthorized Headers:' response. I am able to successfully connect to the instance using Postman and the non-interactive credentials just fine. In the traceback there is the following error note:

--> 126 decode_b64=self.translate_to_boolean(kwargs.get("decode_b64", False)))

other than that I am unable to determine where in the header the problem lies. Is there a tool in TM1py to help diagnose? Something that would give me the header info actually sent? Is the problem in the encoding of the UID/PW?

Thanks, Dean

rkvinoth commented 4 years ago

It does work for me in version 1.4.1

from TM1py.Services import TM1Service

tm1 = TM1Service(base_url='https://mycloudid.planning-analytics.ibmcloud.com/tm1/api/mytm1server/', 
                 user="mynoninteractivID", namespace="LDAP", password="mynoninteractivePW", ssl=True)
print(tm1.server.get_server_name())

I would suggest you to double check the base_url, user and password argument values again. If you are passing the base64 encoded password (since you mentioned Postman), try the below approach.

Is the problem in the encoding of the UID/PW?

You can basically use decode_b64 argument just like user or namespace and tell TM1py that you are already passing an encoded password, then TM1py will directly use that.

from TM1py.Services import TM1Service

tm1 = TM1Service(base_url='https://mycloudid.planning-analytics.ibmcloud.com/tm1/api/mytm1server/', 
                 user="mynoninteractivID", namespace="LDAP", password="encrypted_password", 
                 decode_b64=True, ssl=True)
print(tm1.server.get_server_name())
dltswatts commented 4 years ago

No, unfortunately nothing seems to be working. I've verified all of the information and I tried the use of the decode_b64 flag and still getting same results. A head scratcher.

rkvinoth commented 4 years ago

Post the entire stack trace here.. will try to check what's going on.

vishwesh999 commented 4 years ago

When we connect to IBM cloud application, we are also providing the port number as one of the parameters in the connection string - Can you include it and try again

On Fri, Jul 24, 2020 at 2:52 PM Vinoth Kumar Ravi notifications@github.com wrote:

Post the entire stack trace here.. will try to check what's going on.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/cubewise-code/tm1py/issues/298#issuecomment-663706158, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQJ3KKY6BNSIVRFULDRNUATR5HRALANCNFSM4PGYXM2Q .

-- Vishwesh Patil

dltswatts commented 4 years ago

Here is the full trace, slightly modified to mask the instance, server and UID/PW:

TM1pyExceptionTraceback (most recent call last)

in ----> 1 tm1 = TM1Service(base_url="https://_myinstance_.planning-analytics.ibmcloud.com/tm1/api/_myserver_/", user="_mynonineractiveUID_", password="_mynoninteractivePW_", namespace="LDAP", ssl=True) /opt/conda/lib/python3.7/site-packages/TM1py/Services/TM1Service.py in __init__(self, **kwargs) 10 """ 11 def __init__(self, **kwargs): ---> 12 self._tm1_rest = RESTService(**kwargs) 13 14 # instantiate all Services /opt/conda/lib/python3.7/site-packages/TM1py/Services/RESTService.py in __init__(self, **kwargs) 124 namespace=kwargs.get("namespace", None), 125 gateway=kwargs.get("gateway", None), --> 126 decode_b64=self.translate_to_boolean(kwargs.get("decode_b64", False))) 127 128 # manage connection pool /opt/conda/lib/python3.7/site-packages/TM1py/Services/RESTService.py in _start_session(self, user, password, decode_b64, namespace, gateway) 252 request = '/api/v1/Configuration/ProductVersion/$value' 253 try: --> 254 response = self.GET(request=request) 255 self._version = response.text 256 finally: /opt/conda/lib/python3.7/site-packages/TM1py/Services/RESTService.py in wrapper(self, request, data, odata_escape_single_quotes_in_object_names) 41 response = func(self, request, data) 42 # Verify ---> 43 self.verify_response(response=response) 44 return response 45 /opt/conda/lib/python3.7/site-packages/TM1py/Services/RESTService.py in verify_response(response) 327 status_code=response.status_code, 328 reason=response.reason, --> 329 headers=response.headers) 330 331 @staticmethod TM1pyException: Text: Status Code: 401 Reason: Unauthorized Headers: {'Date': 'Fri, 24 Jul 2020 19:27:48 GMT', 'Server': 'Apache', 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains', 'Content-Type': 'text/plain', 'Content-Length': '0', 'WWW-Authenticate': 'CAMPassport https://_myinstance_.planning-analytics.ibmcloud.com/ibmcognos/bi/v1/disp, CAMNamespace', 'Set-Cookie': 'TM1SessionId=OlXBs4uLMI_WL41hdjozvA; Path=/tm1/api/_myserver_/; HttpOnly; Secure', 'X-Content-Type-Options': 'nosniff', 'Keep-Alive': 'timeout=5, max=100', 'Connection': 'Keep-Alive'}
rkvinoth commented 4 years ago

When we connect to IBM cloud application, we are also providing the port number as one of the parameters in the connection string - Can you include it and try again On Fri, Jul 24, 2020 at 2:52 PM Vinoth Kumar Ravi @.***> wrote: Post the entire stack trace here.. will try to check what's going on. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub <#298 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AQJ3KKY6BNSIVRFULDRNUATR5HRALANCNFSM4PGYXM2Q . -- Vishwesh Patil

@vishwesh999 If you use base_url connection parameter then port parameter is not necessary and even if you pass it will remain as a dummy variable.

rkvinoth commented 4 years ago

@dltswatts

  1. If I pass in a wrong user or password, I get the same Unauthorized Headers. Again try to check your username and password.
  2. Try using a python environment without conda (I don't see any other obvious reasons).
MariusWirtz commented 4 years ago

@dltswatts, bear in mind that after you tried to authenticate with invalid credentials too many times, your user may be blocked for a short period.

Agree with @rkvinoth that anaconda is a potential source of trouble. Best to go with plain python and install the handful of libraries needed.

dltswatts commented 4 years ago

First, thanks to all for your time on this. I greatly appreciate it.

I have been successful running requests from Postman against this instance using the same credentials so they are verified. The only difference is that with Postman I have encoded the authorization string myself. I have been trying to run the code in Jupyter but per your suggestions ran the code from the python command line and got the same error message.

I then ran the following code from python, again using the exact same credentials but providing the encoding myself, and it ran successfully:

import requests

url = "https://_myinstance_.planning-analytics.ibmcloud.com/tm1/api/_myserver_/api/v1/Cubes"

payload = "" headers = { 'Authorization': "CAMNamespace mycredentialencoding", 'cache-control': "no-cache" }

response = requests.request("GET", url, data=payload, headers=headers)

print(response.text)

My speculation is that there is something going on in TM1py in either the encoding or the communication of the encoded string in the header that is not acceptable to this instance. In general I know it usually works on prem and others here have successfully tested to their IBM Cloud instances. Is there some way to get what TM1py is sending as the Authorization in the header?

Thanks again for all your collective help.

rkvinoth commented 4 years ago

Basically all requests related work are taken care in the RestService. Once TM1py establishes the connection, it will drop the Authorization header. So, I don't think there is any way to check the Authorization header through TM1Service.

But, in RestService we have a static method for generating the Authorization header. Call that method explicitly by passing the credentials and it would return the result you are expecting (the below code will do).

from TM1py.Services import RestService

print(RestService._build_authorization_token(user='test_account', password='test_password', namespace='LDAP'))

# Output : CAMNamespace dGVzdF9hY2NvdW50OnRlc3RfcGFzc3dvcmQ6TERBUA==
dltswatts commented 4 years ago

Yes, I found that function and was able to input my variables and observe the output. I was able to determine that the encoded value that TM1py generates is exactly the same as the value I create myself. So I would guess that something else that is sent in the header is causing the instance to balk. I will have to go through each value that is listed in the error message and is generated in the RestService file to determine what it might be.

MariusWirtz commented 4 years ago

Before you go down this road, make sure you upgrade to the master branch on GitHub (the de facto latest version of TM1py)

pip install https://github.com/cubewise-code/tm1py/archive/master.zip --upgrade

MariusWirtz commented 4 years ago

@dltswatts did you find a solution to this problem? Can we close this issue?

dltswatts commented 4 years ago

@MariusWirtz no, I have not found the solution but lets close this issue. I may need to bring up with IBM as I believe it is something with the instance (business partner). Go ahead and close.