Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to the Zowe Client Python SDK will be documented in this file.

## Recent Changes

### Enhancements
- Added get_log_messages console REST API function. [#391](https://github.com/zowe/zowe-client-python-sdk/pull/391)
- Added exec-data and status query parameters to list_jobs. [#391](https://github.com/zowe/zowe-client-python-sdk/pull/391)
- Corrected members parsing, list_members enhancement. [#391](https://github.com/zowe/zowe-client-python-sdk/pull/391)

## `1.0.0-dev26`

### Bug Fixes
Expand Down
36 changes: 16 additions & 20 deletions src/core/zowe/core_for_zowe_sdk/request_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
Copyright Contributors to the Zowe Project.
"""

from typing import Union, Any
from requests import Response
from typing import Any, Union

import requests
import urllib3
from requests import Response

from .exceptions import InvalidRequestMethod, RequestFailed, UnexpectedStatus
from .logger import Log
Expand Down Expand Up @@ -115,27 +115,23 @@ def __validate_response(self) -> None:
------
UnexpectedStatus
If the response status code is not in the expected code list
RequestFailed
If the HTTP/HTTPS request fails
"""
# 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}\n"
f"Expected: {self.__response.status_code}\n"
f"Request output: {self.__response.text}"
)
raise UnexpectedStatus(self.__expected_code, self.__response.status_code, self.__response.text)
if self.__response.status_code in self.__expected_code:
return
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}"
diagnostics = (
f"The status code from z/OSMF was: {self.__response.status_code}\n"
f"Expected: {self.__expected_code}\n"
f"Request URL: {self.__response.request.url}\n"
f"Request headers: {self.__response.request.headers}\n"
f"Request body: {self.__response.request.body}\n"
f"Request output: {self.__response.text}"
)
raise RequestFailed(self.__response.status_code, output_str)
if self.__response.ok:
self.__logger.warning(diagnostics)
else:
self.__logger.error(diagnostics)
raise UnexpectedStatus(self.__expected_code, self.__response.status_code, self.__response.text)

def __normalize_response(self) -> Union[str, bytes, dict[str, Any], None]:
"""
Expand Down
115 changes: 106 additions & 9 deletions src/zos_console/zowe/zos_console_for_zowe_sdk/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
Copyright Contributors to the Zowe Project.
"""

from typing import Optional, Any
from datetime import datetime, timezone
from typing import Any, Literal, Optional

from zowe.core_for_zowe_sdk import SdkApi

from .response import ConsoleResponse, IssueCommandResponse
from .response import (
ConsoleResponse,
GetLogMessagesResponse,
IssueCommandResponse,
UnsuccessfulGetLogMessagesResponse,
)


class Console(SdkApi): # type: ignore
Expand All @@ -30,9 +36,14 @@ class Console(SdkApi): # type: ignore
"""

def __init__(self, connection: dict[str, Any], log: bool = True):
super().__init__(connection, "/zosmf/restconsoles/consoles/defcn", logger_name=__name__, log=log)

def issue_command(self, command: str, console: Optional[str] = None) -> IssueCommandResponse:
super().__init__(connection, "/zosmf/restconsoles/", logger_name=__name__, log=log)

def issue_command(
self,
command: str,
console: Optional[str] = None,
system: Optional[str] = None
) -> IssueCommandResponse:
"""Issues a command on z/OS Console.

Parameters
Expand All @@ -41,15 +52,20 @@ def issue_command(self, command: str, console: Optional[str] = None) -> IssueCom
The z/OS command to be executed
console : Optional[str]
Name of the console that should be used to execute the command (default is None)
system : Optional[str]
Name of the system in the same sysplex that the command is routed to
(default is None, that means the local system)

Returns
-------
IssueCommandResponse
A JSON containing the response from the console command
"""
custom_args = self._create_custom_request_arguments()
custom_args["url"] = self._request_endpoint.replace("defcn", console or "defcn")
request_body = {"cmd": command}
custom_args["url"] = "{}consoles/{}".format(self._request_endpoint, console or "defcn")
request_body = {"cmd":command}
if system is not None:
request_body["system"] = system
custom_args["json"] = request_body
response_json = self.request_handler.perform_request("PUT", custom_args)
return IssueCommandResponse(response_json)
Expand All @@ -71,7 +87,88 @@ def get_response(self, response_key: str, console: Optional[str] = None) -> Cons
A JSON containing the response to the command
"""
custom_args = self._create_custom_request_arguments()
request_url = "{}/solmsgs/{}".format(console or "defcn", response_key)
custom_args["url"] = self._request_endpoint.replace("defcn", request_url)
request_url = "{}consoles/{}/solmsgs/{}".format(self._request_endpoint, console or "defcn", response_key)
custom_args["url"] = request_url
response_json = self.request_handler.perform_request("GET", custom_args)
return ConsoleResponse(response_json)

def get_log_messages(
self,
time: Optional[datetime] = None,
timestamp: Optional[int] = None,
time_range: Optional[str] = None,
hardcopy: Optional[Literal["OPERLOG", "SYSLOG"]] = None,
sys_name: Optional[str] = None,
direction: Optional[Literal["backward", "forward"]] = None
) -> GetLogMessagesResponse | UnsuccessfulGetLogMessagesResponse:
"""
Retrieve messages from hardcopy logs on the system.

The maximum return size of the log is 10000.
If more than 10000 logs exist in the timeframe, the system returns the first 10000 logs.

See more at: `Get messages from a hardcopy log`_
.. _Get messages from a hardcopy log:
https://www.ibm.com/docs/en/zos/3.2.0?topic=services-get-messages-from-hardcopy-log

Parameters
----------
time : Optional[datetime]
Specifies when z/OSMF starts to retrieve messages.
This value is used if the timestamp parameter is not specified.
timestamp : Optional[int]
Specifies the UNIX timestamp, which is the number of milliseconds since 1970-01-01 UTC.
This parameter is specified, the "time" parameter is ignored.
time_range : Optional[str]
Specifies the time range for which the log is to be retrieved.
Supported time units include s, m, and h for seconds, minutes, and hours.
The format is nnnu, where nnn is a number 1-999 and u is one of the time units "s", "m", or "h".
For example, 999s of 20m.
The default is 10m.
hardcopy : Optional[Literal['OPERLOG', 'SYSLOG']]
Specify the source where the logs come from.
If not specified, the API tries OPERLOG first.
If the OPERLOG is not enabled on the system, the API returns the SYSLOG.
sys_name : Optional[str]
The name of the system on which the SYSLOG resides.
direction : Optional[Literal['backward', 'forward']]
Specifies the direction (from a specified time) in which messages are retrieved.
The default is "backward", meaning that messages are retrieved backward from the specified time.

Returns
-------
GetLogMessagesResponse | UnsuccessfulGetLogMessagesResponse
A response content for a successful/unsuccessful get messages request response
"""
custom_args = self._create_custom_request_arguments()
custom_args["url"] = "{}v1/log".format(self._request_endpoint)
params = {}
if time is not None and timestamp is not None:
self.logger.warning(
'Both "time" and "timestamp" query parameters are provided. "time" parameter is ignored'
)
time = None
if time is not None:
if time.tzinfo is not None and time.tzinfo != timezone.utc:
self.logger.warning(
f'"time" query parameter is not in UTC timezone ({time.tzinfo}). Conversion to UTC will occur'
)
time = time.astimezone(timezone.utc)
params["time"] = time
if timestamp:
params["timestamp"] = timestamp
if time_range:
params["timeRange"] = time_range
if hardcopy:
params["hardcopy"] = hardcopy
if sys_name:
params["sysName"] = sys_name
if direction:
params["direction"] = direction
custom_args["params"] = params
response_json = self.request_handler.perform_request("GET", custom_args, expected_code=[200, 400, 500])
return (
GetLogMessagesResponse(response_json)
if "returnCode" not in response_json.keys()
else UnsuccessfulGetLogMessagesResponse(response_json)
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@
Copyright Contributors to the Zowe Project.
"""

from .console import ConsoleResponse, IssueCommandResponse
from .console import (
ConsoleResponse,
GetLogMessagesResponse,
IssueCommandResponse,
UnsuccessfulGetLogMessagesResponse,
)
Loading
Loading