Skip to content

Commit

Permalink
Merge pull request #277 from zowe/TSO
Browse files Browse the repository at this point in the history
Tso issue_command
  • Loading branch information
t1m0thyj authored Jun 17, 2024
2 parents cdf6265 + f580bd6 commit 3a15fc1
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 33 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil
- Added logger class to core SDK [#185](https://github.com/zowe/zowe-client-python-sdk/issues/185)
- Added classes for handling `Datasets`, `USSFiles`, and `FileSystems` in favor of the single Files class. [#264](https://github.com/zowe/zowe-client-python-sdk/issues/264)
- Refactored tests into proper folders and files [#265](https://github.com/zowe/zowe-client-python-sdk/issues/265)
- Fix the bug on `upload_file_to_dsn`. [#104](https://github.com/zowe/zowe-client-python-sdk/issues/104)

### Bug Fixes

- Fixed truncated responses when issuing TSO commands [#260](https://github.com/zowe/zowe-client-python-sdk/issues/260)
- Fixed a bug on `upload_file_to_dsn` where it would not properly convert line endings on Windows. [#104](https://github.com/zowe/zowe-client-python-sdk/issues/104)

## `1.0.0-dev15`

Expand Down
2 changes: 2 additions & 0 deletions src/core/zowe/core_for_zowe_sdk/logger.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os


class Log:
"""root logger setup and a function to customize logger level"""

Expand All @@ -16,6 +17,7 @@ class Log:
)

loggers = set()

@staticmethod
def registerLogger(name: str):
logger = logging.getLogger(name)
Expand Down
11 changes: 6 additions & 5 deletions src/core/zowe/core_for_zowe_sdk/profile_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,19 @@
import warnings
from copy import deepcopy
from typing import Optional
from .logger import Log

import jsonschema
from deepmerge import always_merger

from .config_file import ConfigFile, Profile
from .logger import Log
from .credential_manager import CredentialManager
from .custom_warnings import (
ConfigNotFoundWarning,
ProfileNotFoundWarning,
SecurePropsNotFoundWarning,
)
from .exceptions import ProfileNotFound, SecureProfileLoadFailed, SecureValuesNotFound
from .logger import Log
from .profile_constants import (
BASE_PROFILE,
GLOBAL_CONFIG_NAME,
Expand Down Expand Up @@ -219,7 +218,9 @@ def get_profile(
ProfileNotFoundWarning,
)
except Exception as exc:
logger.warning(f"Could not load '{cfg.filename}' at '{cfg.filepath}'" f"because {type(exc).__name__}'{exc}'")
logger.warning(
f"Could not load '{cfg.filename}' at '{cfg.filepath}'" f"because {type(exc).__name__}'{exc}'"
)
warnings.warn(
f"Could not load '{cfg.filename}' at '{cfg.filepath}'" f"because {type(exc).__name__}'{exc}'.",
ConfigNotFoundWarning,
Expand Down Expand Up @@ -253,7 +254,7 @@ def load(
If `profile_type` is not base, then we will load properties from both
`profile_type` and base profiles and merge them together.
"""

if profile_name is None and profile_type is None:
self.__logger.error(f"Failed to load profile as both profile_name and profile_type are not set")
raise ProfileNotFound(
Expand All @@ -273,7 +274,7 @@ def load(
cfg_name = None
cfg_schema = None
cfg_schema_dir = None

for cfg_layer in (self.project_user_config, self.project_config, self.global_user_config, self.global_config):
if cfg_layer.profiles is None:
try:
Expand Down
19 changes: 12 additions & 7 deletions src/core/zowe/core_for_zowe_sdk/request_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import requests
import urllib3
from .logger import Log

from .exceptions import InvalidRequestMethod, RequestFailed, UnexpectedStatus
from .logger import Log
Expand All @@ -30,7 +29,7 @@ class RequestHandler:
List of supported request methods
"""

def __init__(self, session_arguments, logger_name = __name__):
def __init__(self, session_arguments, logger_name=__name__):
"""
Construct a RequestHandler object.
Expand All @@ -52,7 +51,7 @@ def __handle_ssl_warnings(self):
if not self.session_arguments["verify"]:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def perform_request(self, method, request_arguments, expected_code=[200], stream = False):
def perform_request(self, method, request_arguments, expected_code=[200], stream=False):
"""Execute an HTTP/HTTPS requests from given arguments and return validated response (JSON).
Parameters
Expand All @@ -74,9 +73,11 @@ def perform_request(self, method, request_arguments, expected_code=[200], stream
self.method = method
self.request_arguments = request_arguments
self.expected_code = expected_code
self.__logger.debug(f"Request method: {self.method}, Request arguments: {self.request_arguments}, Expected code: {expected_code}")
self.__logger.debug(
f"Request method: {self.method}, Request arguments: {self.request_arguments}, Expected code: {expected_code}"
)
self.__validate_method()
self.__send_request(stream = stream)
self.__send_request(stream=stream)
self.__validate_response()
if stream:
return self.response
Expand Down Expand Up @@ -114,14 +115,18 @@ def __validate_response(self):
# Automatically checks if status code is between 200 and 400
if self.response.ok:
if self.response.status_code not in self.expected_code:
self.__logger.error(f"The status code from z/OSMF was: {self.expected_code}\nExpected: {self.response.status_code}\nRequest output:{self.response.text}")
self.__logger.error(
f"The status code from z/OSMF was: {self.expected_code}\nExpected: {self.response.status_code}\nRequest output:{self.response.text}"
)
raise UnexpectedStatus(self.expected_code, self.response.status_code, self.response.text)
else:
output_str = str(self.response.request.url)
output_str += "\n" + str(self.response.request.headers)
output_str += "\n" + str(self.response.request.body)
output_str += "\n" + str(self.response.text)
self.__logger.error(f"HTTP Request has failed with status code {self.response.status_code}. \n {output_str}")
self.__logger.error(
f"HTTP Request has failed with status code {self.response.status_code}. \n {output_str}"
)
raise RequestFailed(self.response.status_code, output_str)

def __normalize_response(self):
Expand Down
2 changes: 1 addition & 1 deletion src/zos_files/zowe/zos_files_for_zowe_sdk/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def get_dsn_binary_content(self, dataset_name, with_prefixes=False):

def get_dsn_binary_content_streamed(self, dataset_name, with_prefixes=False):
"""Deprecated function. Please use ds.get_binary_content() instead"""
return self.ds.get_binary_content(dataset_name, with_prefixes, stream=True)
return self.ds.get_binary_content(dataset_name, stream=True, with_prefixes=with_prefixes)

def write_to_dsn(self, dataset_name, data, encoding=_ZOWE_FILES_DEFAULT_ENCODING):
"""Deprecated function. Please use ds.write() instead"""
Expand Down
2 changes: 1 addition & 1 deletion src/zos_files/zowe/zos_files_for_zowe_sdk/uss.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def upload(self, input_file, filepath_name, encoding=_ZOWE_FILES_DEFAULT_ENCODIN
"""Upload contents of a given file and uploads it to UNIX file"""
if os.path.isfile(input_file):
with open(input_file, "r", encoding="utf-8") as in_file:
response_json = self.write(filepath_name, in_file)
response_json = self.write(filepath_name, in_file.read())
else:
self.logger.error(f"File {input_file} not found.")
raise FileNotFound(input_file)
9 changes: 9 additions & 0 deletions src/zos_tso/zowe/zos_tso_for_zowe_sdk/tso.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ def issue_command(self, command):
session_key = self.start_tso_session()
command_output = self.send_tso_message(session_key, command)
tso_messages = self.retrieve_tso_messages(command_output)
while not any("TSO PROMPT" in message for message in command_output) or not tso_messages:
command_output = self.__get_tso_data(session_key)
tso_messages += self.retrieve_tso_messages(command_output)
self.end_tso_session(session_key)
return tso_messages

Expand Down Expand Up @@ -202,3 +205,9 @@ def retrieve_tso_messages(self, response_json):
A list containing the TSO response messages
"""
return [message["TSO MESSAGE"]["DATA"] for message in response_json if "TSO MESSAGE" in message]

def __get_tso_data(self, session_key):
custom_args = self._create_custom_request_arguments()
custom_args["url"] = "{}/{}".format(self.request_endpoint, session_key)
command_output = self.request_handler.perform_request("GET", custom_args)["tsoData"]
return command_output
1 change: 0 additions & 1 deletion tests/integration/test_zos_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ def test_upload_download_delete_dataset(self):
def test_upload_download_delete_uss(self):
self.files.upload_file_to_uss(SAMPLE_JCL_FIXTURE_PATH, self.test_uss_upload)
self.files.download_uss(self.test_uss_upload, SAMPLE_JCL_FIXTURE_PATH + ".tmp")

with open(SAMPLE_JCL_FIXTURE_PATH, "r") as in_file:
old_file_content = in_file.read()
with open(SAMPLE_JCL_FIXTURE_PATH + ".tmp", "r") as in_file:
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/files/datasets/test_copy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from unittest import TestCase, mock
from zowe.zos_files_for_zowe_sdk import Files, exceptions, Datasets

from zowe.zos_files_for_zowe_sdk import Files


class TestCreateClass(TestCase):
Expand Down Expand Up @@ -69,4 +70,4 @@ def test_copy_dataset_or_member(self, mock_send_request):
]
for test_case in test_values:
Files(self.test_profile).copy_dataset_or_member(**test_case)
mock_send_request.assert_called()
mock_send_request.assert_called()
10 changes: 2 additions & 8 deletions tests/unit/files/datasets/test_delete.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
from unittest import TestCase, mock

from zowe.zos_files_for_zowe_sdk import Files, exceptions, Datasets
from zowe.zos_files_for_zowe_sdk import Datasets, Files, exceptions


class TestDeleteClass(TestCase):
Expand Down Expand Up @@ -32,17 +32,11 @@ def test_delete_param(self, mock_send_request):
mock_send_request.return_value = mock.Mock(headers={"Content-Type": "application/json"}, status_code=200)
mock_send_request.return_value.json.return_value = {}

test_cases = [
("MY.PDS", 1000, "m1"),
("MY.C", 100, "m2"),
("MY.D", 1000, "member"),
("MY.E", 500, "extended")
]
test_cases = [("MY.PDS", 1000, "m1"), ("MY.C", 100, "m2"), ("MY.D", 1000, "member"), ("MY.E", 500, "extended")]

for dataset_name, volume, member_name in test_cases:
result = self.files_instance.delete_data_set(dataset_name, volume, member_name)
self.assertEqual(result, {})
mock_send_request.assert_called()
prepared_request = mock_send_request.call_args[0][0]
self.assertEqual(prepared_request.method, "DELETE")

28 changes: 21 additions & 7 deletions tests/unit/test_zos_tso.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,24 @@ def test_object_should_be_instance_of_class(self):
@mock.patch("requests.Session.send")
def test_issue_command(self, mock_send_request):
"""Test issuing a command sends a request"""
fake_response = {"servletKey": None, "tsoData": "READY"}
mock_send_request.return_value = mock.Mock(
headers={"Content-Type": "application/json"}, status_code=200, json=lambda: fake_response
)

Tso(self.test_profile).issue_command("TIME")
self.assertEqual(mock_send_request.call_count, 3)
expected = ['READY', 'GO']
message = {"TSO MESSAGE": {
"DATA": expected[0]
}
}
message2 = {"TSO MESSAGE": {
"DATA": expected[1]
}
}
fake_responses = [
mock.Mock(headers={"Content-Type": "application/json"}, status_code=200, json=lambda: {"servletKey": None, "tsoData": [ message]}),
mock.Mock(headers={"Content-Type": "application/json"}, status_code=200, json=lambda: {"servletKey": None, "tsoData": [ message]}),
mock.Mock(headers={"Content-Type": "application/json"}, status_code=200, json=lambda: {"servletKey": None, "tsoData": ["TSO PROMPT", message2]}),
mock.Mock(headers={"Content-Type": "application/json"}, status_code=200, json=lambda: {"servletKey": None, "tsoData": [ message]}),
]

mock_send_request.side_effect = fake_responses

result = Tso(self.test_profile).issue_command("TIME")
self.assertEqual(result, expected)
self.assertEqual(mock_send_request.call_count, 4)

0 comments on commit 3a15fc1

Please sign in to comment.