From d346a8556889c21a5d1b09970dd170e334fe23d2 Mon Sep 17 00:00:00 2001 From: Cate Guyman Date: Tue, 21 Jan 2025 10:29:33 -0800 Subject: [PATCH 1/4] add new script for smoke test --- samples/list.py | 4 +- samples/vizql_data_service_smoke_test_cap.py | 79 +++++++++++++++++++ .../server/endpoint/auth_endpoint.py | 18 ++--- 3 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 samples/vizql_data_service_smoke_test_cap.py diff --git a/samples/list.py b/samples/list.py index 2675a2954..2c7e9eff9 100644 --- a/samples/list.py +++ b/samples/list.py @@ -58,8 +58,8 @@ def main(): count = 0 for resource in TSC.Pager(endpoint.get, options): count = count + 1 - # endpoint.populate_connections(resource) - print(resource.name[:18], " ") # , resource._connections()) + endpoint.populate_connections(resource) + print(resource.name[:18], " ") , resource._connections()) if count > 100: break print(f"Total: {count}") diff --git a/samples/vizql_data_service_smoke_test_cap.py b/samples/vizql_data_service_smoke_test_cap.py new file mode 100644 index 000000000..7d38243c7 --- /dev/null +++ b/samples/vizql_data_service_smoke_test_cap.py @@ -0,0 +1,79 @@ +#### +# This script demonstrates how to query for permissions using TSC +# To run the script, you must have installed Python 3.7 or later. +# +# Example usage: 'python query_permissions.py -s https://10ax.online.tableau.com --site +# devSite123 -u tabby@tableau.com b4065286-80f0-11ea-af1b-cb7191f48e45' +#### + +import argparse +import logging + +import tableauserverclient as TSC + + +def main(): + parser = argparse.ArgumentParser(description="Query permissions of a given resource.") + # Common options; please keep those in sync across all samples + parser.add_argument("--server", "-s", help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server") + parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server") + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) + parser.add_argument("resource_id") + + args = parser.parse_args() + # Convert Namespace to dictionary + args_dict = vars(args) + + # Format the dictionary as a string + args_str = ', '.join(f'{key}={value}' for key, value in args_dict.items()) + + print(args_str) + # Set logging level based on user input, or error by default + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + # Sign in + tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) + server = TSC.Server(args.server, use_server_version=True) + with server.auth.sign_in(tableau_auth): + # Mapping to grab the handler for the user-inputted resource type + # endpoint = { + # "workbook": server.workbooks, + # "datasource": server.datasources, + # "flow": server.flows, + # "table": server.tables, + # "database": server.databases, + # }.get("datasource") + endpoint = server.datasources + + # Get the resource by its ID + resource = endpoint.get_by_id(args.resource_id) + + # Populate permissions for the resource + endpoint.populate_permissions(resource) + permissions = resource.permissions + + # Print result + print(f"\n{len(permissions)} permission rule(s) found for workbook {args.resource_id}.") + + for permission in permissions: + grantee = permission.grantee + capabilities = permission.capabilities + print(f"\nCapabilities for {grantee.tag_name} {grantee.id}:") + + for capability in capabilities: + print(f"\t{capability} - {capabilities[capability]}") + + +if __name__ == "__main__": + main() + + diff --git a/tableauserverclient/server/endpoint/auth_endpoint.py b/tableauserverclient/server/endpoint/auth_endpoint.py index 35dfa5d78..0401d05f2 100644 --- a/tableauserverclient/server/endpoint/auth_endpoint.py +++ b/tableauserverclient/server/endpoint/auth_endpoint.py @@ -23,8 +23,8 @@ def __init__(self, callback): def __enter__(self): return self - def __exit__(self, exc_type, exc_val, exc_tb): - self._callback() + # def __exit__(self, exc_type, exc_val, exc_tb): + # self._callback() @property def baseurl(self) -> str: @@ -106,13 +106,13 @@ def sign_in_with_json_web_token(self, auth_req: "Credentials") -> contextmgr: @api(version="2.0") def sign_out(self) -> None: """Sign out of current session.""" - url = f"{self.baseurl}/signout" - # If there are no auth tokens you're already signed out. No-op - if not self.parent_srv.is_signed_in(): - return - self.post_request(url, "") - self.parent_srv._clear_auth() - logger.info("Signed out") + # url = f"{self.baseurl}/signout" + # # If there are no auth tokens you're already signed out. No-op + # if not self.parent_srv.is_signed_in(): + # return + # self.post_request(url, "") + # self.parent_srv._clear_auth() + # logger.info("Signed out") @api(version="2.6") def switch_site(self, site_item: "SiteItem") -> contextmgr: From 282be608a5a0f7e90a30c6d742d9f04dab5b77ab Mon Sep 17 00:00:00 2001 From: Cate Guyman Date: Tue, 21 Jan 2025 10:32:58 -0800 Subject: [PATCH 2/4] resetting list.py --- samples/list.py | 4 ++-- samples/vizql_data_service_smoke_test_cap.py | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/samples/list.py b/samples/list.py index 2c7e9eff9..2675a2954 100644 --- a/samples/list.py +++ b/samples/list.py @@ -58,8 +58,8 @@ def main(): count = 0 for resource in TSC.Pager(endpoint.get, options): count = count + 1 - endpoint.populate_connections(resource) - print(resource.name[:18], " ") , resource._connections()) + # endpoint.populate_connections(resource) + print(resource.name[:18], " ") # , resource._connections()) if count > 100: break print(f"Total: {count}") diff --git a/samples/vizql_data_service_smoke_test_cap.py b/samples/vizql_data_service_smoke_test_cap.py index 7d38243c7..fe0691bf6 100644 --- a/samples/vizql_data_service_smoke_test_cap.py +++ b/samples/vizql_data_service_smoke_test_cap.py @@ -44,14 +44,6 @@ def main(): tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - # Mapping to grab the handler for the user-inputted resource type - # endpoint = { - # "workbook": server.workbooks, - # "datasource": server.datasources, - # "flow": server.flows, - # "table": server.tables, - # "database": server.databases, - # }.get("datasource") endpoint = server.datasources # Get the resource by its ID From 2b0b966adf6dede09435068b5262c27ec3461c5a Mon Sep 17 00:00:00 2001 From: Cate Guyman Date: Tue, 21 Jan 2025 13:44:53 -0800 Subject: [PATCH 3/4] calling hbi endpoint --- samples/vizql_data_service_smoke_test_cap.py | 33 +++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/samples/vizql_data_service_smoke_test_cap.py b/samples/vizql_data_service_smoke_test_cap.py index fe0691bf6..60c23d6ac 100644 --- a/samples/vizql_data_service_smoke_test_cap.py +++ b/samples/vizql_data_service_smoke_test_cap.py @@ -8,6 +8,8 @@ import argparse import logging +import requests +import json import tableauserverclient as TSC @@ -28,14 +30,6 @@ def main(): ) parser.add_argument("resource_id") - args = parser.parse_args() - # Convert Namespace to dictionary - args_dict = vars(args) - - # Format the dictionary as a string - args_str = ', '.join(f'{key}={value}' for key, value in args_dict.items()) - - print(args_str) # Set logging level based on user input, or error by default logging_level = getattr(logging, args.logging_level.upper()) logging.basicConfig(level=logging_level) @@ -43,26 +37,27 @@ def main(): # Sign in tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) + with server.auth.sign_in(tableau_auth): endpoint = server.datasources # Get the resource by its ID resource = endpoint.get_by_id(args.resource_id) + print(server) + + + url = "https://" + args.server + "/api/v1/vizql-data-service/read-metadata" - # Populate permissions for the resource - endpoint.populate_permissions(resource) - permissions = resource.permissions + payload = "{\n \"datasource\": {\n \"datasourceLuid\": \"" + args.resource_id + "\"\n },\n \"options\": {\n \"debug\": true\n }\n}" + headers = { + 'X-Tableau-Auth': server.auth_token, + 'Content-Type': 'application/json', + } - # Print result - print(f"\n{len(permissions)} permission rule(s) found for workbook {args.resource_id}.") + response = requests.request("POST", url, headers=headers, data=payload) - for permission in permissions: - grantee = permission.grantee - capabilities = permission.capabilities - print(f"\nCapabilities for {grantee.tag_name} {grantee.id}:") + print(response.text) - for capability in capabilities: - print(f"\t{capability} - {capabilities[capability]}") if __name__ == "__main__": From b838d028cd6ece5efba04b7e2ac80302d34bcbd4 Mon Sep 17 00:00:00 2001 From: Cate Guyman Date: Tue, 21 Jan 2025 15:32:49 -0800 Subject: [PATCH 4/4] remove dependency on TSC --- samples/vizql_data_service_smoke_test_cap.py | 94 ++++++++++++++----- ...ql_data_service_smoke_test_cap_with_TSC.py | 86 +++++++++++++++++ 2 files changed, 154 insertions(+), 26 deletions(-) create mode 100644 samples/vizql_data_service_smoke_test_cap_with_TSC.py diff --git a/samples/vizql_data_service_smoke_test_cap.py b/samples/vizql_data_service_smoke_test_cap.py index 60c23d6ac..987fd8c57 100644 --- a/samples/vizql_data_service_smoke_test_cap.py +++ b/samples/vizql_data_service_smoke_test_cap.py @@ -1,22 +1,57 @@ #### -# This script demonstrates how to query for permissions using TSC +# This script is for smoke testing VizQL Data Service. # To run the script, you must have installed Python 3.7 or later. # -# Example usage: 'python query_permissions.py -s https://10ax.online.tableau.com --site -# devSite123 -u tabby@tableau.com b4065286-80f0-11ea-af1b-cb7191f48e45' +# Example usage: 'python vizql_data_service_smoke_test_cap.py --token-name token +# --token-value DUCg0rbPROuuAMz9rDI4+Q==:OM2SzwPVK7dNITHo4nfUgsTZvVBQj8iQ +# --server stage-dataplane7.online.vnext.tabint.net +# --site hbistagedp7 +# --datasouce-luid ebe79f30-bdff-425a-8a9c-3cda79dbbbfd +# --cap 2000' + #### import argparse import logging import requests import json +import xml.etree.ElementTree as ET + +def parse_token_from_response(xml_response): + namespace = {'ns': 'http://tableau.com/api'} + root = ET.fromstring(xml_response) + + # Find the token attribute in the credentials tag + credentials = root.find('ns:credentials', namespace) + if credentials is not None: + token = credentials.get('token') + return token + else: + raise ValueError("Token not found in the XML response.") + + +def sign_in(args): + url = f"https://{args.server}/api/3.22/auth/signin" + + payload = json.dumps({ + "credentials": { + "personalAccessTokenName": args.token_name, + "personalAccessTokenSecret": args.token_value, + "site": { + "contentUrl": args.site + } + } + }) + headers = { + 'Content-Type': 'application/json' + } -import tableauserverclient as TSC - + response = requests.request("POST", url, headers=headers, data=payload) + auth_token = parse_token_from_response(response.text) + return auth_token def main(): parser = argparse.ArgumentParser(description="Query permissions of a given resource.") - # Common options; please keep those in sync across all samples parser.add_argument("--server", "-s", help="server address") parser.add_argument("--site", "-S", help="site name") parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server") @@ -28,39 +63,46 @@ def main(): default="error", help="desired logging level (set to error by default)", ) - parser.add_argument("resource_id") + parser.add_argument("--datasource-luid", "-ds", help="The luid of the datasource to query", required=True) + parser.add_argument("--cap", "-c", type=int, help="The cap on the current cloud site", required=True) + + args = parser.parse_args() # Set logging level based on user input, or error by default logging_level = getattr(logging, args.logging_level.upper()) logging.basicConfig(level=logging_level) # Sign in - tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) - server = TSC.Server(args.server, use_server_version=True) - - with server.auth.sign_in(tableau_auth): - endpoint = server.datasources - - # Get the resource by its ID - resource = endpoint.get_by_id(args.resource_id) - print(server) + auth_token = sign_in(args) + url = "https://" + args.server + "/api/v1/vizql-data-service/read-metadata" - url = "https://" + args.server + "/api/v1/vizql-data-service/read-metadata" - - payload = "{\n \"datasource\": {\n \"datasourceLuid\": \"" + args.resource_id + "\"\n },\n \"options\": {\n \"debug\": true\n }\n}" - headers = { - 'X-Tableau-Auth': server.auth_token, - 'Content-Type': 'application/json', + payload = json.dumps({ + "datasource": { + "datasourceLuid": args.datasource_luid } + }) + headers = { + 'X-Tableau-Auth': auth_token, + 'Content-Type': 'application/json', + } - response = requests.request("POST", url, headers=headers, data=payload) + # Test cap limit + for i in range(args.cap + 1): + response = requests.post(url, headers=headers, data=payload) + status_code = response.status_code - print(response.text) + if i < args.cap and status_code != 200: + response_message = response.text + exceptionMsg = f"Unexpected status code for call {i + 1}: {status_code} (Expected: 200). Response message: {response_message}"; + raise Exception(exceptionMsg) + elif i >= args.cap and status_code != 429: + exceptionMsg = f"Call not rate limited: Unexpected status code for call {i + 1}: {status_code} (Expected: 429)"; + raise Exception(exceptionMsg) + logging.info(f"Call {i + 1}/{args.cap}: Status Code {status_code}") + print(f"Completed {args.cap} calls to VizQL Data Service.") if __name__ == "__main__": main() - - diff --git a/samples/vizql_data_service_smoke_test_cap_with_TSC.py b/samples/vizql_data_service_smoke_test_cap_with_TSC.py new file mode 100644 index 000000000..752f35fbf --- /dev/null +++ b/samples/vizql_data_service_smoke_test_cap_with_TSC.py @@ -0,0 +1,86 @@ +#### +# This script is for smoke testing VizQL Data Service. +# To run the script, you must have installed Python 3.7 or later. +# +# Example usage: 'python vizql_data_service_smoke_test_cap.py --token-name token +# --token-value DUCg0rbPROuuAMz9rDI4+Q==:OM2SzwPVK7dNITHo4nfUgsTZvVBQj8iW +# --server stage-dataplane7.online.vnext.tabint.net +# --site hbistagedp7 +# --datasouce-luid ebe79f30-bdff-425a-8a9c-3cda79dbbbfd +# --cap 2000' + +#### + +import argparse +import logging +import requests +import json + +import tableauserverclient as TSC + + +def main(): + parser = argparse.ArgumentParser(description="Query permissions of a given resource.") + parser.add_argument("--server", "-s", help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server") + parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server") + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) + parser.add_argument("--datasource-luid", "-ds", help="The luid of the datasource to query", required=True) + parser.add_argument("--cap", "-c", type=int, help="The cap on the current cloud site", required=True) + + args = parser.parse_args() + + # Set logging level based on user input, or error by default + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + # Sign in + tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) + server = TSC.Server(args.server, use_server_version=True) + + with server.auth.sign_in(tableau_auth): + url = "https://" + args.server + "/api/v1/vizql-data-service/read-metadata" + + payload = json.dumps({ + "datasource": { + "datasourceLuid": args.datasource_luid + }, + "options": { + "debug": True + } + }) + headers = { + 'X-Tableau-Auth': server.auth_token, + 'Content-Type': 'application/json', + } + + # Test cap limit + for i in range(args.cap + 1): + response = requests.post(url, headers=headers, data=payload) + status_code = response.status_code + + if i < args.cap and status_code != 200: + response_message = response.text + exceptionMsg = f"Unexpected status code for call {i + 1}: {status_code} (Expected: 200). Response message: {response_message}"; + raise Exception(exceptionMsg) + elif i >= args.cap and status_code != 429: + exceptionMsg = f"Call not rate limited: Unexpected status code for call {i + 1}: {status_code} (Expected: 429)"; + raise Exception(exceptionMsg) + + logging.info(f"Call {i + 1}/{args.cap}: Status Code {status_code}") + + print(f"Completed {args.cap} calls to VizQL Data Service.") + + + +if __name__ == "__main__": + main() + +