Skip to content

Commit 45c9b39

Browse files
committed
Added get_log_messages console REST API function, added exec-data and status query parameters to list_jobs, corrected members parsing, list_members enhancement
Signed-off-by: Uladzislau <leksilonchikk@gmail.com>
1 parent 75ff182 commit 45c9b39

13 files changed

Lines changed: 958 additions & 78 deletions

File tree

src/core/zowe/core_for_zowe_sdk/request_handler.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
Copyright Contributors to the Zowe Project.
1111
"""
1212

13-
from typing import Union, Any
14-
from requests import Response
13+
from typing import Any, Union
1514

1615
import requests
1716
import urllib3
17+
from requests import Response
1818

1919
from .exceptions import InvalidRequestMethod, RequestFailed, UnexpectedStatus
2020
from .logger import Log
@@ -115,27 +115,23 @@ def __validate_response(self) -> None:
115115
------
116116
UnexpectedStatus
117117
If the response status code is not in the expected code list
118-
RequestFailed
119-
If the HTTP/HTTPS request fails
120118
"""
121-
# Automatically checks if status code is between 200 and 400
122-
if self.__response.ok:
123-
if self.__response.status_code not in self.__expected_code:
124-
self.__logger.error(
125-
f"The status code from z/OSMF was: {self.__expected_code}\n"
126-
f"Expected: {self.__response.status_code}\n"
127-
f"Request output: {self.__response.text}"
128-
)
129-
raise UnexpectedStatus(self.__expected_code, self.__response.status_code, self.__response.text)
119+
if self.__response.status_code in self.__expected_code:
120+
return
130121
else:
131-
output_str = str(self.__response.request.url)
132-
output_str += "\n" + str(self.__response.request.headers)
133-
output_str += "\n" + str(self.__response.request.body)
134-
output_str += "\n" + str(self.__response.text)
135-
self.__logger.error(
136-
f"HTTP Request has failed with status code {self.__response.status_code}. \n {output_str}"
122+
diagnostics = (
123+
f"The status code from z/OSMF was: {self.__response.status_code}\n"
124+
f"Expected: {self.__expected_code}\n"
125+
f"Request URL: {self.__response.request.url}\n"
126+
f"Request headers: {self.__response.request.headers}\n"
127+
f"Request body: {self.__response.request.body}\n"
128+
f"Request output: {self.__response.text}"
137129
)
138-
raise RequestFailed(self.__response.status_code, output_str)
130+
if self.__response.ok:
131+
self.__logger.warning(diagnostics)
132+
else:
133+
self.__logger.error(diagnostics)
134+
raise UnexpectedStatus(self.__expected_code, self.__response.status_code, self.__response.text)
139135

140136
def __normalize_response(self) -> Union[str, bytes, dict[str, Any], None]:
141137
"""

src/zos_console/zowe/zos_console_for_zowe_sdk/console.py

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@
1010
Copyright Contributors to the Zowe Project.
1111
"""
1212

13-
from typing import Optional, Any
13+
from datetime import datetime, timezone
14+
from typing import Any, Literal, Optional
1415

1516
from zowe.core_for_zowe_sdk import SdkApi
1617

17-
from .response import ConsoleResponse, IssueCommandResponse
18+
from .response import (
19+
ConsoleResponse,
20+
GetLogMessagesResponse,
21+
IssueCommandResponse,
22+
UnsuccessfulGetLogMessagesResponse,
23+
)
1824

1925

2026
class Console(SdkApi): # type: ignore
@@ -30,9 +36,14 @@ class Console(SdkApi): # type: ignore
3036
"""
3137

3238
def __init__(self, connection: dict[str, Any], log: bool = True):
33-
super().__init__(connection, "/zosmf/restconsoles/consoles/defcn", logger_name=__name__, log=log)
34-
35-
def issue_command(self, command: str, console: Optional[str] = None) -> IssueCommandResponse:
39+
super().__init__(connection, "/zosmf/restconsoles/", logger_name=__name__, log=log)
40+
41+
def issue_command(
42+
self,
43+
command: str,
44+
console: Optional[str] = None,
45+
system: Optional[str] = None
46+
) -> IssueCommandResponse:
3647
"""Issues a command on z/OS Console.
3748
3849
Parameters
@@ -41,15 +52,20 @@ def issue_command(self, command: str, console: Optional[str] = None) -> IssueCom
4152
The z/OS command to be executed
4253
console : Optional[str]
4354
Name of the console that should be used to execute the command (default is None)
55+
system : Optional[str]
56+
Name of the system in the same sysplex that the command is routed to
57+
(default is None, that means the local system)
4458
4559
Returns
4660
-------
4761
IssueCommandResponse
4862
A JSON containing the response from the console command
4963
"""
5064
custom_args = self._create_custom_request_arguments()
51-
custom_args["url"] = self._request_endpoint.replace("defcn", console or "defcn")
52-
request_body = {"cmd": command}
65+
custom_args["url"] = "{}consoles/{}".format(self._request_endpoint, console or "defcn")
66+
request_body = {"cmd":command}
67+
if system is not None:
68+
request_body["system"] = system
5369
custom_args["json"] = request_body
5470
response_json = self.request_handler.perform_request("PUT", custom_args)
5571
return IssueCommandResponse(response_json)
@@ -71,7 +87,88 @@ def get_response(self, response_key: str, console: Optional[str] = None) -> Cons
7187
A JSON containing the response to the command
7288
"""
7389
custom_args = self._create_custom_request_arguments()
74-
request_url = "{}/solmsgs/{}".format(console or "defcn", response_key)
75-
custom_args["url"] = self._request_endpoint.replace("defcn", request_url)
90+
request_url = "{}consoles/{}/solmsgs/{}".format(self._request_endpoint, console or "defcn", response_key)
91+
custom_args["url"] = request_url
7692
response_json = self.request_handler.perform_request("GET", custom_args)
7793
return ConsoleResponse(response_json)
94+
95+
def get_log_messages(
96+
self,
97+
time: Optional[datetime] = None,
98+
timestamp: Optional[int] = None,
99+
time_range: Optional[str] = None,
100+
hardcopy: Optional[Literal["OPERLOG", "SYSLOG"]] = None,
101+
sys_name: Optional[str] = None,
102+
direction: Optional[Literal["backward", "forward"]] = None
103+
) -> GetLogMessagesResponse | UnsuccessfulGetLogMessagesResponse:
104+
"""
105+
Retrieve messages from hardcopy logs on the system.
106+
107+
The maximum return size of the log is 10000.
108+
If more than 10000 logs exist in the timeframe, the system returns the first 10000 logs.
109+
110+
See more at: `Get messages from a hardcopy log`_
111+
.. _Get messages from a hardcopy log:
112+
https://www.ibm.com/docs/en/zos/3.2.0?topic=services-get-messages-from-hardcopy-log
113+
114+
Parameters
115+
----------
116+
time : Optional[datetime]
117+
Specifies when z/OSMF starts to retrieve messages.
118+
This value is used if the timestamp parameter is not specified.
119+
timestamp : Optional[int]
120+
Specifies the UNIX timestamp, which is the number of milliseconds since 1970-01-01 UTC.
121+
This parameter is specified, the "time" parameter is ignored.
122+
time_range : Optional[str]
123+
Specifies the time range for which the log is to be retrieved.
124+
Supported time units include s, m, and h for seconds, minutes, and hours.
125+
The format is nnnu, where nnn is a number 1-999 and u is one of the time units "s", "m", or "h".
126+
For example, 999s of 20m.
127+
The default is 10m.
128+
hardcopy : Optional[Literal['OPERLOG', 'SYSLOG']]
129+
Specify the source where the logs come from.
130+
If not specified, the API tries OPERLOG first.
131+
If the OPERLOG is not enabled on the system, the API returns the SYSLOG.
132+
sys_name : Optional[str]
133+
The name of the system on which the SYSLOG resides.
134+
direction : Optional[Literal['backward', 'forward']]
135+
Specifies the direction (from a specified time) in which messages are retrieved.
136+
The default is "backward", meaning that messages are retrieved backward from the specified time.
137+
138+
Returns
139+
-------
140+
GetLogMessagesResponse | UnsuccessfulGetLogMessagesResponse
141+
A response content for a successful/unsuccessful get messages request response
142+
"""
143+
custom_args = self._create_custom_request_arguments()
144+
custom_args["url"] = "{}v1/log".format(self._request_endpoint)
145+
params = {}
146+
if time is not None and timestamp is not None:
147+
self.logger.warning(
148+
'Both "time" and "timestamp" query parameters are provided. "time" parameter is ignored'
149+
)
150+
time = None
151+
if time is not None:
152+
if time.tzinfo is not None and time.tzinfo != timezone.utc:
153+
self.logger.warning(
154+
f'"time" query parameter is not in UTC timezone ({time.tzinfo}). Conversion to UTC will occur'
155+
)
156+
time = time.astimezone(timezone.utc)
157+
params["time"] = time
158+
if timestamp:
159+
params["timestamp"] = timestamp
160+
if time_range:
161+
params["timeRange"] = time_range
162+
if hardcopy:
163+
params["hardcopy"] = hardcopy
164+
if sys_name:
165+
params["sysName"] = sys_name
166+
if direction:
167+
params["direction"] = direction
168+
custom_args["params"] = params
169+
response_json = self.request_handler.perform_request("GET", custom_args, expected_code=[200, 400, 500])
170+
return (
171+
GetLogMessagesResponse(response_json)
172+
if "returnCode" not in response_json.keys()
173+
else UnsuccessfulGetLogMessagesResponse(response_json)
174+
)

src/zos_console/zowe/zos_console_for_zowe_sdk/response/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@
1010
Copyright Contributors to the Zowe Project.
1111
"""
1212

13-
from .console import ConsoleResponse, IssueCommandResponse
13+
from .console import (
14+
ConsoleResponse,
15+
GetLogMessagesResponse,
16+
IssueCommandResponse,
17+
UnsuccessfulGetLogMessagesResponse,
18+
)

src/zos_console/zowe/zos_console_for_zowe_sdk/response/console.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,31 @@
1010
Copyright Contributors to the Zowe Project.
1111
"""
1212

13+
import re
1314
from dataclasses import dataclass
15+
from datetime import datetime, timedelta
16+
from datetime import timezone as tz
1417
from typing import Any, Optional
1518

19+
CAMEL_TO_SNAKE_CASE_PATTERN = re.compile(r'(?<!^)(?=[A-Z])')
20+
21+
22+
def to_snake_case(src_str: str) -> str:
23+
"""
24+
Convert camel case string (cameCaseString) to a snake case string (snake_case_string).
25+
26+
Parameters
27+
----------
28+
src_str : str
29+
The string to convert
30+
31+
Returns
32+
-------
33+
str
34+
The converted string
35+
"""
36+
return CAMEL_TO_SNAKE_CASE_PATTERN.sub('_', src_str).lower()
37+
1638

1739
@dataclass
1840
class IssueCommandResponse:
@@ -48,3 +70,139 @@ def __getitem__(self, key: str) -> Any:
4870

4971
def __setitem__(self, key: str, value: Any) -> None:
5072
self.__dict__[key.replace("-", "_")] = value
73+
74+
75+
@dataclass
76+
class LogMessageResponse:
77+
"""
78+
Log message response object.
79+
See more at [Messages JSON object](https://www.ibm.com/docs/en/zos/3.2.0?topic=services-get-messages-from-hardcopy-log#IZUHPINFO_API_GetMessagesandLogs.dita__table_qby_q2x_ypb)
80+
81+
Parameters
82+
----------
83+
cart : Optional[str]
84+
Eight character command and response token (CART).
85+
color : Optional[str]
86+
The color of the message.
87+
job_name : Optional[str]
88+
The name of the job that generates the message.
89+
message : Optional[str]
90+
The content of the message.
91+
message_id : Optional[str]
92+
The message ID.
93+
reply_id : Optional[str]
94+
Reply ID, in decimal.
95+
system : Optional[str]
96+
Original eight character system name.
97+
type : Optional[str]
98+
HARDCOPY.
99+
sub_type : Optional[str]
100+
Indicate whether the message is a DOM, WTOR, or HOLD message.
101+
time : Optional[datetime]
102+
For example, “Thu Feb 03 03:00 GMT 2021”.
103+
timestamp : Optional[int]
104+
UNIX timestamp (milliseconds since epoch). For example, 1621920830109.
105+
"""
106+
107+
cart: Optional[str] = None
108+
color: Optional[str] = None
109+
job_name: Optional[str] = None
110+
message: Optional[str] = None
111+
message_id: Optional[str] = None
112+
reply_id: Optional[str] = None
113+
system: Optional[str] = None
114+
type: Optional[str] = None
115+
sub_type: Optional[str] = None
116+
time: Optional[datetime] = None
117+
timestamp: Optional[int] = None
118+
119+
def __init__(self, raw_data: dict[str, Any]) -> None:
120+
for raw_key, raw_value in raw_data.items():
121+
key = to_snake_case(raw_key)
122+
match(key):
123+
case "time":
124+
value = datetime.strptime(raw_value, "%a %b %d %H:%M:%S %Z %Y")
125+
case _:
126+
value = raw_value
127+
super().__setattr__(key, value)
128+
129+
def __getitem__(self, key: str) -> Any:
130+
return self.__dict__[to_snake_case(key)]
131+
132+
def __setitem__(self, key: str, value: Any) -> None:
133+
self.__dict__[to_snake_case(key)] = value
134+
135+
136+
@dataclass
137+
class GetLogMessagesResponse:
138+
"""
139+
Get log messages response object.
140+
See more at [Response content for a successful Get Messages request](https://www.ibm.com/docs/en/zos/3.2.0?topic=services-get-messages-from-hardcopy-log#IZUHPINFO_API_GetMessagesandLogs.dita__getcmdresponse)
141+
142+
Parameters
143+
----------
144+
timezone : Optional[tz]
145+
The timezone of the z/OS system.
146+
total_items : Optional[int]
147+
Total number of messages returned in the response.
148+
next_timestamp : Optional[int]
149+
The UNIX timestamp (milliseconds).
150+
items : Optional[list[LogMessageResponse]]
151+
Array of log messages.
152+
source : Optional[str]
153+
Indicates the source of the messages.
154+
"""
155+
timezone: Optional[tz] = None
156+
total_items: Optional[int] = None
157+
next_timestamp: Optional[int] = None
158+
items: Optional[list[LogMessageResponse]] = None
159+
source: Optional[str] = None
160+
161+
def __init__(self, response: dict[str, Any]) -> None:
162+
for raw_key, raw_value in response.items():
163+
key = to_snake_case(raw_key)
164+
match(key):
165+
case "timezone":
166+
value = tz(timedelta(hours=raw_value if raw_value is not None else 0))
167+
case "items":
168+
value = [LogMessageResponse(x) for x in raw_value] if not raw_value == None else None
169+
case _:
170+
value = raw_value
171+
super().__setattr__(key, value)
172+
173+
def __getitem__(self, key: str) -> Any:
174+
return self.__dict__[to_snake_case(key)]
175+
176+
def __setitem__(self, key: str, value: Any) -> None:
177+
self.__dict__[to_snake_case(key)] = value
178+
179+
180+
@dataclass
181+
class UnsuccessfulGetLogMessagesResponse:
182+
"""
183+
Get log messages response object for an unsuccessful request.
184+
See more at [Response content for an unsuccessful Get Messages request](https://www.ibm.com/docs/en/zos/3.2.0?topic=services-get-messages-from-hardcopy-log#IZUHPINFO_API_GetMessagesandLogs.dita__table_b2g_sgx_ypb)
185+
186+
Parameters
187+
----------
188+
return_code : Optional[int]
189+
Identifies the category of error.
190+
reason_code : Optional[int]
191+
Identifies the specific error.
192+
reason : Optional[str]
193+
Text that describes the cause of the error.
194+
"""
195+
return_code: Optional[int] = None
196+
reason_code: Optional[int] = None
197+
reason: Optional[str] = None
198+
199+
def __init__(self, response: dict[str, Any]) -> None:
200+
for raw_key, value in response.items():
201+
key = to_snake_case(raw_key)
202+
super().__setattr__(key, value)
203+
204+
def __getitem__(self, key):
205+
return self.__dict__[to_snake_case(key)]
206+
207+
def __setitem__(self, key: str, value: Any) -> None:
208+
self.__dict__[to_snake_case(key)] = value

0 commit comments

Comments
 (0)