From c2278f12fa1c6c557dee75905dfedcfb4263f971 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 27 Nov 2024 12:40:25 +0530 Subject: [PATCH 01/23] feat: add systems client code for get jobs API. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/systems/__init__.py | 1 + .../clients/systems/_systems_client.py | 70 +++++++++++++++++++ .../clients/systems/models/__init__.py | 2 + nisystemlink/clients/systems/models/_job.py | 55 +++++++++++++++ .../clients/systems/models/_job_config.py | 19 +++++ .../clients/systems/models/_job_state.py | 12 ++++ 6 files changed, 159 insertions(+) create mode 100644 nisystemlink/clients/systems/__init__.py create mode 100644 nisystemlink/clients/systems/_systems_client.py create mode 100644 nisystemlink/clients/systems/models/__init__.py create mode 100644 nisystemlink/clients/systems/models/_job.py create mode 100644 nisystemlink/clients/systems/models/_job_config.py create mode 100644 nisystemlink/clients/systems/models/_job_state.py diff --git a/nisystemlink/clients/systems/__init__.py b/nisystemlink/clients/systems/__init__.py new file mode 100644 index 00000000..6a580aa5 --- /dev/null +++ b/nisystemlink/clients/systems/__init__.py @@ -0,0 +1 @@ +from ._systems_client import SystemsClient diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/systems/_systems_client.py new file mode 100644 index 00000000..797bbdef --- /dev/null +++ b/nisystemlink/clients/systems/_systems_client.py @@ -0,0 +1,70 @@ +from typing import Optional, List +from uplink import Query + +from nisystemlink.clients import core +from nisystemlink.clients.core._uplink._base_client import BaseClient +from nisystemlink.clients.core._uplink._methods import ( + get, + post, +) + +from . import models + + +class SystemsClient(BaseClient): + def __init__(self, configuration: Optional[core.HttpConfiguration] = None): + """Initialize an instance. + + Args: + configuration: Defines the web server to connect to and information about + how to connect. If not provided, the + :class:`HttpConfigurationManager ` + is used to obtain the configuration. + + Raises: + ApiException: if unable to communicate with the Systems Service. + """ + if configuration is None: + configuration = core.HttpConfigurationManager.get_configuration() + + super().__init__(configuration, "/nisysmgmt/v1/") + + @get( + "jobs", + args=[ + Query("systemId"), + Query("jid"), + Query("state"), + Query("function"), + Query("skip"), + Query("take"), + ], + ) + def list_jobs( + self, + system_id: Optional[str] = None, + jid: Optional[str] = None, + state: Optional[models.JobState] = None, + function: Optional[str] = None, + skip: Optional[int] = None, + take: Optional[int] = None, + ) -> List[models.Job]: + """List the jobs that matched the criteria. + + Args: + system_id: The ID of the system to that the jobs belong. + jid: The ID of the job. + state: The state of the jobs. + function: The salt function to run on the client. + skip: The number of jobs to skip. + take: The number of jobs to return. + + Returns: + The list of jobs that matched the criteria. + + Raises: + ApiException: if unable to communicate with the Systems Service + or provided an invalid argument. + """ + ... + diff --git a/nisystemlink/clients/systems/models/__init__.py b/nisystemlink/clients/systems/models/__init__.py new file mode 100644 index 00000000..e9a8e772 --- /dev/null +++ b/nisystemlink/clients/systems/models/__init__.py @@ -0,0 +1,2 @@ +from ._job_state import JobState +from ._job import Job diff --git a/nisystemlink/clients/systems/models/_job.py b/nisystemlink/clients/systems/models/_job.py new file mode 100644 index 00000000..ff07f64b --- /dev/null +++ b/nisystemlink/clients/systems/models/_job.py @@ -0,0 +1,55 @@ +from typing import Dict, List, Optional +from datetime import datetime + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._job_state import JobState +from ._job_config import JobConfig + + +class JobResult(JsonModel): + jid: Optional[str] = None + """The job ID.""" + + id: Optional[str] = None + """The ID of the system that the job targets.""" + + retcode: Optional[List[int]] = None + """Return code of the job.""" + + return_: Optional[List[str]] = None + """Return value of the job.""" + + success: Optional[bool] = None + """Whether the job was successful.""" + + +class Job(JsonModel): + """Job information.""" + + jid: Optional[str] = None + """The job ID.""" + + id: Optional[str] = None + """The ID of the system that the job targets.""" + + created_timestamp: Optional[datetime] = None + """The timestamp representing when the job was created.""" + + last_updated_timestamp: Optional[datetime] = None + """The timestamp representing when the job was last updated.""" + + dispatched_timestamp: Optional[datetime] = None + """The timestamp representing when the job was dispatched.""" + + state: Optional[JobState] = None + """The state of the job.""" + + metadata: Optional[Dict[str, str]] = None + """The metadata associated with the job.""" + + config: Optional[JobConfig] = None + """The configuration of the job.""" + + result: Optional[JobResult] = None + """The result of the job.""" diff --git a/nisystemlink/clients/systems/models/_job_config.py b/nisystemlink/clients/systems/models/_job_config.py new file mode 100644 index 00000000..906f40c7 --- /dev/null +++ b/nisystemlink/clients/systems/models/_job_config.py @@ -0,0 +1,19 @@ +from typing import List, Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class JobConfig(JsonModel): + """The configuration of the job.""" + + user: Optional[str] = None + """The user who created the job.""" + + tgt: Optional[List[str]] = None + """The target systems for the job.""" + + fun: Optional[List[str]] = None + """Salt functions related to the job.""" + + arg: Optional[List[str]] = None + """Arguments of the salt functions.""" diff --git a/nisystemlink/clients/systems/models/_job_state.py b/nisystemlink/clients/systems/models/_job_state.py new file mode 100644 index 00000000..1cf90f45 --- /dev/null +++ b/nisystemlink/clients/systems/models/_job_state.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class JobState(Enum): + """The state of the jobs.""" + + SUCCEEDED = "SUCCEEDED" + OUTOFQUEUE = "OUTOFQUEUE" + INQUEUE = "INQUEUE" + INPROGRESS = "INPROGRESS" + CANCELED = "CANCELED" + FAILED = "FAILED" From cbba5c7502853100b12ecd2dfcd50dc54e804c71 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 27 Nov 2024 12:42:14 +0530 Subject: [PATCH 02/23] feat: add systems client code for create job API. Signed-off-by: Ganesh Nithin --- .../clients/systems/_systems_client.py | 16 +++++++++++ .../clients/systems/models/__init__.py | 2 ++ .../systems/models/_create_job_request.py | 10 +++++++ .../systems/models/_create_job_response.py | 14 ++++++++++ .../clients/systems/models/_http_error.py | 28 +++++++++++++++++++ 5 files changed, 70 insertions(+) create mode 100644 nisystemlink/clients/systems/models/_create_job_request.py create mode 100644 nisystemlink/clients/systems/models/_create_job_response.py create mode 100644 nisystemlink/clients/systems/models/_http_error.py diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/systems/_systems_client.py index 797bbdef..cf160cb1 100644 --- a/nisystemlink/clients/systems/_systems_client.py +++ b/nisystemlink/clients/systems/_systems_client.py @@ -68,3 +68,19 @@ def list_jobs( """ ... + @post("jobs") + def create_job(self, job: models.CreateJobRequest) -> models.CreateJobResponse: + """Create a job. + + Args: + job: The request to create the job. + + Returns: + The job that was created. + + Raises: + ApiException: if unable to communicate with the Systems Service + or provided an invalid argument. + """ + ... + diff --git a/nisystemlink/clients/systems/models/__init__.py b/nisystemlink/clients/systems/models/__init__.py index e9a8e772..d7b33fe5 100644 --- a/nisystemlink/clients/systems/models/__init__.py +++ b/nisystemlink/clients/systems/models/__init__.py @@ -1,2 +1,4 @@ from ._job_state import JobState from ._job import Job +from ._create_job_request import CreateJobRequest +from ._create_job_response import CreateJobResponse diff --git a/nisystemlink/clients/systems/models/_create_job_request.py b/nisystemlink/clients/systems/models/_create_job_request.py new file mode 100644 index 00000000..f08f2c08 --- /dev/null +++ b/nisystemlink/clients/systems/models/_create_job_request.py @@ -0,0 +1,10 @@ +from typing import Dict, Optional, Any + +from ._job_config import JobConfig + + +class CreateJobRequest(JobConfig): + """An instance of NationalInstruments.SystemsManagementService.Model.API.CreateJobRequest.""" + + metadata: Optional[Dict[str, Any]] = None + """The metadata associated with the job.""" diff --git a/nisystemlink/clients/systems/models/_create_job_response.py b/nisystemlink/clients/systems/models/_create_job_response.py new file mode 100644 index 00000000..33923b20 --- /dev/null +++ b/nisystemlink/clients/systems/models/_create_job_response.py @@ -0,0 +1,14 @@ +from typing import Optional + +from ._create_job_request import CreateJobRequest +from ._http_error import HttpError + + +class CreateJobResponse(CreateJobRequest): + """The job that was created.""" + + error: Optional[HttpError] = None + """Represents the standard error structure.""" + + jid: Optional[str] = None + """The job ID.""" diff --git a/nisystemlink/clients/systems/models/_http_error.py b/nisystemlink/clients/systems/models/_http_error.py new file mode 100644 index 00000000..4d29bff7 --- /dev/null +++ b/nisystemlink/clients/systems/models/_http_error.py @@ -0,0 +1,28 @@ +from typing import List, Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class HttpError(JsonModel): + """Represents the standard error structure.""" + + name: Optional[str] = None + """Gets the fully-qualified name that identifies the error code, such as the output of M:NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.ToString.""" + + code: Optional[int] = None + """Gets the optional NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.NumericCode (not HTTP status code) that identifies the error code.""" + + message: Optional[str] = None + """Gets the formatted error message, which is the NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.Message with NationalInstruments.SystemLink.ServiceBase.HttpError.Args inserted into any placeholders.""" + + resourceType: Optional[str] = None + """Gets the type of resource associated with the error, if any. Typically only used for instances within NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" + + resourceId: Optional[str] = None + """Gets the ID of the resource associated with the error, if any. Typically only used for instances within NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" + + args: Optional[List[str]] = None + """Gets the arguments that produced NationalInstruments.SystemLink.ServiceBase.HttpError.Message.""" + + innerErrors: Optional[List["HttpError"]] = None + """Gets any nested errors if this instance represents more than one error. Typically only set for instances related to NationalInstruments.SystemLink.ServiceBase.ErrorHandling.SkylineErrorCodes.OneOrMoreErrorsOccurred.""" From 258f37393c7524b15a043bdbba9c3cc1e39c8fed Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 27 Nov 2024 12:43:26 +0530 Subject: [PATCH 03/23] feat: add systems client code for get jobs summary API. Signed-off-by: Ganesh Nithin --- .../clients/systems/_systems_client.py | 13 ++++++++++++ .../clients/systems/models/__init__.py | 1 + .../systems/models/_job_summary_response.py | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 nisystemlink/clients/systems/models/_job_summary_response.py diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/systems/_systems_client.py index cf160cb1..d3b5228b 100644 --- a/nisystemlink/clients/systems/_systems_client.py +++ b/nisystemlink/clients/systems/_systems_client.py @@ -84,3 +84,16 @@ def create_job(self, job: models.CreateJobRequest) -> models.CreateJobResponse: """ ... + @get("get-jobs-summary") + def get_job_summary(self) -> models.JobSummaryResponse: + """Get a summary of the jobs. + + Returns: + An instance of a NationalInstruments.SystemsManagementService.Model.API.JobsSummaryResponse. + + Raises: + ApiException: if unable to communicate with the Systems Service + or provided an invalid argument. + """ + ... + diff --git a/nisystemlink/clients/systems/models/__init__.py b/nisystemlink/clients/systems/models/__init__.py index d7b33fe5..afbb7c81 100644 --- a/nisystemlink/clients/systems/models/__init__.py +++ b/nisystemlink/clients/systems/models/__init__.py @@ -2,3 +2,4 @@ from ._job import Job from ._create_job_request import CreateJobRequest from ._create_job_response import CreateJobResponse +from ._job_summary_response import JobSummaryResponse diff --git a/nisystemlink/clients/systems/models/_job_summary_response.py b/nisystemlink/clients/systems/models/_job_summary_response.py new file mode 100644 index 00000000..dff7e3dc --- /dev/null +++ b/nisystemlink/clients/systems/models/_job_summary_response.py @@ -0,0 +1,21 @@ +from typing import Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._http_error import HttpError + + +class JobSummaryResponse(JsonModel): + """An instance of a NationalInstruments.SystemsManagementService.Model.API.JobsSummaryResponse.""" + + error: Optional[HttpError] = None + """Represents the standard error structure.""" + + activeCount: Optional[int] = None + """The number of active jobs.""" + + failedCount: Optional[int] = None + """The number of failed jobs.""" + + succeededCount: Optional[int] = None + """The number of succeeded jobs.""" From 4e22c2847027ae99ef66215aeefe994375b37344 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 27 Nov 2024 12:44:46 +0530 Subject: [PATCH 04/23] feat: add systems client code for query jobs API. Signed-off-by: Ganesh Nithin --- .../clients/systems/_systems_client.py | 16 +++++ .../clients/systems/models/__init__.py | 2 + .../systems/models/_query_jobs_request.py | 62 +++++++++++++++++++ .../systems/models/_query_jobs_response.py | 18 ++++++ 4 files changed, 98 insertions(+) create mode 100644 nisystemlink/clients/systems/models/_query_jobs_request.py create mode 100644 nisystemlink/clients/systems/models/_query_jobs_response.py diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/systems/_systems_client.py index d3b5228b..a0c71c3e 100644 --- a/nisystemlink/clients/systems/_systems_client.py +++ b/nisystemlink/clients/systems/_systems_client.py @@ -97,3 +97,19 @@ def get_job_summary(self) -> models.JobSummaryResponse: """ ... + @post("query-jobs") + def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse: + """Query the jobs. + + Args: + query: The request to query the jobs. + + Returns: + The response to the query. + + Raises: + ApiException: if unable to communicate with the Systems Service + or provided an invalid argument. + """ + ... + diff --git a/nisystemlink/clients/systems/models/__init__.py b/nisystemlink/clients/systems/models/__init__.py index afbb7c81..2124150b 100644 --- a/nisystemlink/clients/systems/models/__init__.py +++ b/nisystemlink/clients/systems/models/__init__.py @@ -3,3 +3,5 @@ from ._create_job_request import CreateJobRequest from ._create_job_response import CreateJobResponse from ._job_summary_response import JobSummaryResponse +from ._query_jobs_request import QueryJobsRequest +from ._query_jobs_response import QueryJobsResponse diff --git a/nisystemlink/clients/systems/models/_query_jobs_request.py b/nisystemlink/clients/systems/models/_query_jobs_request.py new file mode 100644 index 00000000..98e2feb3 --- /dev/null +++ b/nisystemlink/clients/systems/models/_query_jobs_request.py @@ -0,0 +1,62 @@ +from typing import Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +class QueryJobsRequest(JsonModel): + """An instance of NationalInstruments.SystemsManagementService.Model.API.QueryJobsRequest.""" + + skip: Optional[int] = None + """The number of jobs to skip.""" + + take: Optional[int] = None + """The number of jobs to return. The maximum value is 1000.""" + + filter: Optional[str] = None + """ + Gets or sets the filter criteria for jobs or systems. Consists of a string of queries composed using AND/OR operators. + String values and date strings need to be enclosed in double quotes. Parenthesis can be used around filters to better + define the order of operations. Filter syntax: '[property name][operator][operand] and [property name][operator][operand]' + + Operators: + Equals operator '='. Example: 'x = y' + Not equal operator '!='. Example: 'x != y' + Greater than operator '>'. Example: 'x > y' + Greater than or equal operator '>='. Example: 'x >= y' + Less than operator '<'. Example: 'x < y' + Less than or equal operator '<='. Example: 'x <= y' + Logical AND operator 'and' or '&&'. Example: 'x and y' + Logical OR operator 'or' or '||'. Example: 'x or y' + Contains operator '.Contains()', used to check if a list contains an element. Example: 'x.Contains(y)' + Not Contains operator '!.Contains()', used to check if a list does not contain an element. Example: '!x.Contains(y)' + + Valid job properties that can be used in the filter: + jid : String representing the ID of the job. + id : String representing the ID of the system. + createdTimestamp: ISO-8601 formatted timestamp string specifying the date when the job was created. + lastUpdatedTimestamp: ISO-8601 formatted timestamp string specifying the last date the job was updated. + dispatchedTimestamp: ISO-8601 formatted timestamp string specifying the date when the job was actually sent to the system. + state: String representing the state of the job. + metadata: Object containg the the metadata of job. Example: metadata.queued + config.user: String representing the user who created the job. + config.tgt: List of strings representing the targeted systems. Example: config.tgt.Contains("id") + config.fun: List of strings representing the functions to be executed within the job. Example: config.fun.Contains("nisysmgmt.set_blackout") + config.arg: An array of arrays of variable type elements that are arguments to the function specified by the "fun" property. Example: config.arg[0].Contains("test") + result.return: An array of objects representing return values for each executed function. Example: result.return[0].Contains("Success") + result.retcode: An array of integers representing code values for each executed function. Example: result.retcode + result.success: An array of booleans representing success values for each executed function. Example: result.success.Contains(false) + """ + + projection: Optional[str] = None + """ + Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. + + Examples: - 'new(id,jid,state)' - 'new(id,jid,config.user as user)' - 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' + """ + + order_by: Optional[str] = None + """ + The order in which the jobs return. + + Example: createdTimestamp descending + """ + diff --git a/nisystemlink/clients/systems/models/_query_jobs_response.py b/nisystemlink/clients/systems/models/_query_jobs_response.py new file mode 100644 index 00000000..9d45c38f --- /dev/null +++ b/nisystemlink/clients/systems/models/_query_jobs_response.py @@ -0,0 +1,18 @@ +from typing import List, Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._http_error import HttpError + + +class QueryJobsResponse(JsonModel): + """Model for response of a query request.""" + + error: Optional[HttpError] = None + """Represents the standard error structure.""" + + data: Optional[List[str]] = None + """The data returned by the query.""" + + count: Optional[int] = None + """The total number of resources that matched the query.""" From 6070c7120f637957e2682d245a6e3290173244dd Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 27 Nov 2024 12:45:16 +0530 Subject: [PATCH 05/23] feat: add systems client code for cancel jobs API. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/systems/_systems_client.py | 17 +++++++++++++++++ nisystemlink/clients/systems/models/__init__.py | 2 ++ .../systems/models/_cancel_job_request.py | 13 +++++++++++++ .../systems/models/_cancel_job_response.py | 12 ++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 nisystemlink/clients/systems/models/_cancel_job_request.py create mode 100644 nisystemlink/clients/systems/models/_cancel_job_response.py diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/systems/_systems_client.py index a0c71c3e..8a8bf7df 100644 --- a/nisystemlink/clients/systems/_systems_client.py +++ b/nisystemlink/clients/systems/_systems_client.py @@ -113,3 +113,20 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse """ ... + @post("cancel-jobs") + def cancel_jobs( + self, job_ids: List[models.CancelJobRequest] + ) -> models.CancelJobResponse: + """Cancel the jobs. + + Args: + job_ids: List of CancelJobRequest. + + Returns: + The errors that appear while attempting the operation. + + Raises: + ApiException: if unable to communicate with the Systems Service + or provided an invalid argument. + """ + ... diff --git a/nisystemlink/clients/systems/models/__init__.py b/nisystemlink/clients/systems/models/__init__.py index 2124150b..28ac5464 100644 --- a/nisystemlink/clients/systems/models/__init__.py +++ b/nisystemlink/clients/systems/models/__init__.py @@ -5,3 +5,5 @@ from ._job_summary_response import JobSummaryResponse from ._query_jobs_request import QueryJobsRequest from ._query_jobs_response import QueryJobsResponse +from ._cancel_job_request import CancelJobRequest +from ._cancel_job_response import CancelJobResponse diff --git a/nisystemlink/clients/systems/models/_cancel_job_request.py b/nisystemlink/clients/systems/models/_cancel_job_request.py new file mode 100644 index 00000000..94c242e0 --- /dev/null +++ b/nisystemlink/clients/systems/models/_cancel_job_request.py @@ -0,0 +1,13 @@ +from typing import Dict, List, Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class CancelJobRequest(JsonModel): + """Cancel job modal.""" + + jid: Optional[str] = None + """The ID of the job to cancel.""" + + system_id: Optional[str] = None + """The system ID that the job to cancel targets.""" diff --git a/nisystemlink/clients/systems/models/_cancel_job_response.py b/nisystemlink/clients/systems/models/_cancel_job_response.py new file mode 100644 index 00000000..e229cf00 --- /dev/null +++ b/nisystemlink/clients/systems/models/_cancel_job_response.py @@ -0,0 +1,12 @@ +from typing import Dict, List, Optional + +from nisystemlink.clients.core._uplink._json_model import JsonModel + +from ._http_error import HttpError + + +class CancelJobResponse(JsonModel): + """Model for response of a cancel job request.""" + + error: Optional[HttpError] = None + """Represents the standard error structure.""" From 55684d8a84ae6e9e816cdab78cc57514a84efb05 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 27 Nov 2024 16:22:56 +0530 Subject: [PATCH 06/23] refactor: modify docs strings for systems client. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/systems/_systems_client.py | 4 ++-- nisystemlink/clients/systems/models/_cancel_job_request.py | 2 +- nisystemlink/clients/systems/models/_create_job_request.py | 2 +- nisystemlink/clients/systems/models/_create_job_response.py | 2 +- nisystemlink/clients/systems/models/_job.py | 2 +- nisystemlink/clients/systems/models/_job_state.py | 2 +- nisystemlink/clients/systems/models/_job_summary_response.py | 2 +- nisystemlink/clients/systems/models/_query_jobs_request.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/systems/_systems_client.py index 8a8bf7df..0859bda9 100644 --- a/nisystemlink/clients/systems/_systems_client.py +++ b/nisystemlink/clients/systems/_systems_client.py @@ -89,7 +89,7 @@ def get_job_summary(self) -> models.JobSummaryResponse: """Get a summary of the jobs. Returns: - An instance of a NationalInstruments.SystemsManagementService.Model.API.JobsSummaryResponse. + An instance of a JobsSummaryResponse. Raises: ApiException: if unable to communicate with the Systems Service @@ -105,7 +105,7 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse query: The request to query the jobs. Returns: - The response to the query. + An instance of QueryJobsRequest. Raises: ApiException: if unable to communicate with the Systems Service diff --git a/nisystemlink/clients/systems/models/_cancel_job_request.py b/nisystemlink/clients/systems/models/_cancel_job_request.py index 94c242e0..041d3de3 100644 --- a/nisystemlink/clients/systems/models/_cancel_job_request.py +++ b/nisystemlink/clients/systems/models/_cancel_job_request.py @@ -4,7 +4,7 @@ class CancelJobRequest(JsonModel): - """Cancel job modal.""" + """Model for cancel job request.""" jid: Optional[str] = None """The ID of the job to cancel.""" diff --git a/nisystemlink/clients/systems/models/_create_job_request.py b/nisystemlink/clients/systems/models/_create_job_request.py index f08f2c08..9dd5a282 100644 --- a/nisystemlink/clients/systems/models/_create_job_request.py +++ b/nisystemlink/clients/systems/models/_create_job_request.py @@ -4,7 +4,7 @@ class CreateJobRequest(JobConfig): - """An instance of NationalInstruments.SystemsManagementService.Model.API.CreateJobRequest.""" + """Model for create job request.""" metadata: Optional[Dict[str, Any]] = None """The metadata associated with the job.""" diff --git a/nisystemlink/clients/systems/models/_create_job_response.py b/nisystemlink/clients/systems/models/_create_job_response.py index 33923b20..bc12a4a3 100644 --- a/nisystemlink/clients/systems/models/_create_job_response.py +++ b/nisystemlink/clients/systems/models/_create_job_response.py @@ -5,7 +5,7 @@ class CreateJobResponse(CreateJobRequest): - """The job that was created.""" + """Model for response of create job request.""" error: Optional[HttpError] = None """Represents the standard error structure.""" diff --git a/nisystemlink/clients/systems/models/_job.py b/nisystemlink/clients/systems/models/_job.py index ff07f64b..35645dc5 100644 --- a/nisystemlink/clients/systems/models/_job.py +++ b/nisystemlink/clients/systems/models/_job.py @@ -25,7 +25,7 @@ class JobResult(JsonModel): class Job(JsonModel): - """Job information.""" + """Job Model.""" jid: Optional[str] = None """The job ID.""" diff --git a/nisystemlink/clients/systems/models/_job_state.py b/nisystemlink/clients/systems/models/_job_state.py index 1cf90f45..30a55539 100644 --- a/nisystemlink/clients/systems/models/_job_state.py +++ b/nisystemlink/clients/systems/models/_job_state.py @@ -2,7 +2,7 @@ class JobState(Enum): - """The state of the jobs.""" + """The state of the job.""" SUCCEEDED = "SUCCEEDED" OUTOFQUEUE = "OUTOFQUEUE" diff --git a/nisystemlink/clients/systems/models/_job_summary_response.py b/nisystemlink/clients/systems/models/_job_summary_response.py index dff7e3dc..fcbcee2c 100644 --- a/nisystemlink/clients/systems/models/_job_summary_response.py +++ b/nisystemlink/clients/systems/models/_job_summary_response.py @@ -6,7 +6,7 @@ class JobSummaryResponse(JsonModel): - """An instance of a NationalInstruments.SystemsManagementService.Model.API.JobsSummaryResponse.""" + """Model for request of jobs summary response.""" error: Optional[HttpError] = None """Represents the standard error structure.""" diff --git a/nisystemlink/clients/systems/models/_query_jobs_request.py b/nisystemlink/clients/systems/models/_query_jobs_request.py index 98e2feb3..9a9dec47 100644 --- a/nisystemlink/clients/systems/models/_query_jobs_request.py +++ b/nisystemlink/clients/systems/models/_query_jobs_request.py @@ -3,7 +3,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel class QueryJobsRequest(JsonModel): - """An instance of NationalInstruments.SystemsManagementService.Model.API.QueryJobsRequest.""" + """Model for query job request.""" skip: Optional[int] = None """The number of jobs to skip.""" From 412c8efccd49af392117aef791d184e9ca7a557b Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Fri, 29 Nov 2024 07:42:41 +0530 Subject: [PATCH 07/23] feat: add unit tests for jobs in systems client/ Signed-off-by: Ganesh Nithin --- .../clients/systems/models/_job_config.py | 2 +- .../systems/models/_job_summary_response.py | 6 +- .../systems/test_systems_client.py | 259 ++++++++++++++++++ 3 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 tests/integration/systems/test_systems_client.py diff --git a/nisystemlink/clients/systems/models/_job_config.py b/nisystemlink/clients/systems/models/_job_config.py index 906f40c7..40e3136e 100644 --- a/nisystemlink/clients/systems/models/_job_config.py +++ b/nisystemlink/clients/systems/models/_job_config.py @@ -15,5 +15,5 @@ class JobConfig(JsonModel): fun: Optional[List[str]] = None """Salt functions related to the job.""" - arg: Optional[List[str]] = None + arg: Optional[List[List[str]]] = None """Arguments of the salt functions.""" diff --git a/nisystemlink/clients/systems/models/_job_summary_response.py b/nisystemlink/clients/systems/models/_job_summary_response.py index fcbcee2c..6e9df509 100644 --- a/nisystemlink/clients/systems/models/_job_summary_response.py +++ b/nisystemlink/clients/systems/models/_job_summary_response.py @@ -11,11 +11,11 @@ class JobSummaryResponse(JsonModel): error: Optional[HttpError] = None """Represents the standard error structure.""" - activeCount: Optional[int] = None + active_count: Optional[int] = None """The number of active jobs.""" - failedCount: Optional[int] = None + failed_count: Optional[int] = None """The number of failed jobs.""" - succeededCount: Optional[int] = None + succeeded_count: Optional[int] = None """The number of succeeded jobs.""" diff --git a/tests/integration/systems/test_systems_client.py b/tests/integration/systems/test_systems_client.py new file mode 100644 index 00000000..a524fb5a --- /dev/null +++ b/tests/integration/systems/test_systems_client.py @@ -0,0 +1,259 @@ +import pytest +from typing import List, Generator, Callable + +from nisystemlink.clients.core._http_configuration import HttpConfiguration +from nisystemlink.clients.systems import SystemsClient +from nisystemlink.clients.systems.models import ( + CreateJobResponse, + CreateJobRequest, + JobSummaryResponse, + QueryJobsRequest, + CancelJobRequest, +) + + +@pytest.fixture(scope="class") +def client(enterprise_config: HttpConfiguration) -> SystemsClient: + """Fixture to create an SystemsClient instance.""" + return SystemsClient(enterprise_config) + + +@pytest.fixture(scope="class") +def create_job( + client: SystemsClient, +): + """Fixture to create a job.""" + responses: List[CreateJobResponse] = [] + + def _create_job(job: CreateJobRequest) -> CreateJobResponse: + response = client.create_job(job) + responses.append(response) + return response + + yield _create_job + + from nisystemlink.clients.systems.models import CancelJobRequest + + job_requests = [ + CancelJobRequest(jid=response.jid, tgt=response.tgt[0]) + for response in responses + if response.tgt is not None + ] + + client.cancel_jobs(job_requests) + + +@pytest.fixture(scope="class") +def create_multiple_jobs( + create_job, +): + """Fixture to create multiple jobs.""" + responses = [] + arg_1 = [["A description"]] + arg_2 = [["Another description"]] + tgt = ["HVM_domU--SN-ec200972-eeca-062e-5bf5-33g3g3g3d73b2--MAC-0A-E1-20-D6-96-2B"] + fun_1 = ["system.set_computer_desc"] + fun_2 = ["system.set_computer_asc"] + metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + job_1 = CreateJobRequest( + arg=arg_1, + tgt=tgt, + fun=fun_1, + metadata=metadata, + ) + responses.append(create_job(job_1)) + + job_2 = CreateJobRequest( + arg=arg_2, + tgt=tgt, + fun=fun_2, + metadata=metadata, + ) + responses.append(create_job(job_2)) + + return responses + + +@pytest.mark.integration +@pytest.mark.enterprise +class TestSystemsClient: + def test__create_job__succeeds( + self, + create_job, + ): + arg = [["A description"]] + tgt = [ + "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" + ] + fun = ["system.set_computer_desc"] + metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + job = CreateJobRequest( + arg=arg, + tgt=tgt, + fun=fun, + metadata=metadata, + ) + + response = create_job(job) + + assert response is not None + assert response.jid is not None + assert response.arg == arg + assert response.tgt == tgt + assert response.metadata == metadata + assert response.fun == fun + + def test__list_jobs__single_job__succeeds(self, create_job, client: SystemsClient): + arg = [["A description"]] + tgt = [ + "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" + ] + fun = ["system.set_computer_desc"] + metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + job = CreateJobRequest( + arg=arg, + tgt=tgt, + fun=fun, + metadata=metadata, + ) + create_job_response = create_job(job) + + response = client.list_jobs(jid=create_job_response.jid) + assert response is not None + assert len(response) == 1 + + [response_job] = response + + assert response_job.jid == create_job_response.jid + assert response_job.config is not None + assert response_job.config.arg == arg + assert response_job.config.tgt == tgt + assert response_job.metadata == metadata + assert response_job.config.fun == fun + + def test__list_jobs__multiple_jobs__succeeds( + self, create_multiple_jobs, client: SystemsClient + ): + response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0]) + assert response is not None + assert len(response) == 2 + + def test__list_jobs__multiple_jobs_take_one__succeeds( + self, create_multiple_jobs, client: SystemsClient + ): + response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], take=1) + assert response is not None + assert len(response) == 1 + + def test__list_jobs__multiple_jobs_skip_one__succeeds( + self, create_multiple_jobs, client: SystemsClient + ): + response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], skip=1) + assert response is not None + assert len(response) == 1 + + def test__list_jobs__Invalid_system_id__fails(self, client: SystemsClient): + with pytest.raises(Exception): + client.list_jobs(system_id="Invalid_system_id") + + def test__list_jobs__Invalid_jid__fails(self, client: SystemsClient): + with pytest.raises(Exception): + client.list_jobs(jid="Invalid_jid") + + def test__get_job_summary__succeeds(self, client: SystemsClient): + response = client.get_job_summary() + + assert response is not None + assert isinstance(response, JobSummaryResponse) + + def test__query_jobs__take_filter__succeeds(self, client: SystemsClient): + query = QueryJobsRequest(take=1) + response = client.query_jobs(query=query) + + assert response is not None + assert response.count is not None + assert response.data is not None + assert isinstance(response.data, list) + assert response.count == 1 + + def test__query_jobs__config_fun_filter__succeeds(self, client: SystemsClient): + query = QueryJobsRequest( + filter='config.fun.Contains("system.set_computer_desc")' + ) + response = client.query_jobs(query=query) + + assert response is not None + assert response.count is not None + assert response.data is not None + assert isinstance(response.data, list) + assert response.count > 0 + + def test__query_jobs__config_jid_filter__succeeds( + self, create_multiple_jobs, client: SystemsClient + ): + query = QueryJobsRequest(filter=f"jid={create_multiple_jobs[0].jid}") + response = client.query_jobs(query=query) + + assert response is not None + assert response.count is not None + assert response.data is not None + assert isinstance(response.data, list) + assert response.count == 1 + + def test__cancel_jobs__single_job__succeeds(self, client: SystemsClient): + arg = [["A description"]] + tgt = [ + "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" + ] + fun = ["system.set_computer_desc"] + metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + job = CreateJobRequest( + arg=arg, + tgt=tgt, + fun=fun, + metadata=metadata, + ) + response = client.create_job(job) + + cancel_job_request = CancelJobRequest(jid=response.jid, tgt=tgt[0]) + cancel_response = client.cancel_jobs([cancel_job_request]) + + assert cancel_response.error is None + + def test__cancel_jobs__multiple_job__succeeds(self, client: SystemsClient): + arg_1 = [["A description"]] + arg_2 = [["Another description"]] + tgt = [ + "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" + ] + fun = ["system.set_computer_desc"] + metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + job_1 = CreateJobRequest( + arg=arg_1, + tgt=tgt, + fun=fun, + metadata=metadata, + ) + response_1 = client.create_job(job_1) + job_2 = CreateJobRequest( + arg=arg_2, + tgt=tgt, + fun=fun, + metadata=metadata, + ) + response_2 = client.create_job(job_2) + + cancel_job_request_1 = CancelJobRequest(jid=response_1.jid, tgt=tgt[0]) + cancel_job_request_2 = CancelJobRequest(jid=response_2.jid, tgt=tgt[0]) + cancel_response = client.cancel_jobs( + [cancel_job_request_1, cancel_job_request_2] + ) + + assert cancel_response.error is None + + def test__cancel_jobs__Invalid_jid__fails(self, client: SystemsClient): + cancel_job_request = CancelJobRequest(jid="Invalid_jid", tgt="Invalid_tgt") + cancel_response = client.cancel_jobs([cancel_job_request]) + + assert cancel_response.error is not None + assert cancel_response.error.message is not None From 2f91a6c7cffd9cf489a1587fa7e4206448659992 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Fri, 29 Nov 2024 09:33:50 +0530 Subject: [PATCH 08/23] refactor: change name systems into system. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/__init__.py | 1 + .../_system_client.py} | 14 +++---- .../{systems => system}/models/__init__.py | 0 .../models/_cancel_job_request.py | 0 .../models/_cancel_job_response.py | 0 .../models/_create_job_request.py | 0 .../models/_create_job_response.py | 0 .../{systems => system}/models/_http_error.py | 0 .../{systems => system}/models/_job.py | 0 .../{systems => system}/models/_job_config.py | 0 .../{systems => system}/models/_job_state.py | 0 .../models/_job_summary_response.py | 0 .../models/_query_jobs_request.py | 0 .../models/_query_jobs_response.py | 0 nisystemlink/clients/systems/__init__.py | 1 - .../test_system_client.py} | 42 +++++++++---------- 16 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 nisystemlink/clients/system/__init__.py rename nisystemlink/clients/{systems/_systems_client.py => system/_system_client.py} (97%) rename nisystemlink/clients/{systems => system}/models/__init__.py (100%) rename nisystemlink/clients/{systems => system}/models/_cancel_job_request.py (100%) rename nisystemlink/clients/{systems => system}/models/_cancel_job_response.py (100%) rename nisystemlink/clients/{systems => system}/models/_create_job_request.py (100%) rename nisystemlink/clients/{systems => system}/models/_create_job_response.py (100%) rename nisystemlink/clients/{systems => system}/models/_http_error.py (100%) rename nisystemlink/clients/{systems => system}/models/_job.py (100%) rename nisystemlink/clients/{systems => system}/models/_job_config.py (100%) rename nisystemlink/clients/{systems => system}/models/_job_state.py (100%) rename nisystemlink/clients/{systems => system}/models/_job_summary_response.py (100%) rename nisystemlink/clients/{systems => system}/models/_query_jobs_request.py (100%) rename nisystemlink/clients/{systems => system}/models/_query_jobs_response.py (100%) delete mode 100644 nisystemlink/clients/systems/__init__.py rename tests/integration/{systems/test_systems_client.py => system/test_system_client.py} (90%) diff --git a/nisystemlink/clients/system/__init__.py b/nisystemlink/clients/system/__init__.py new file mode 100644 index 00000000..a4d97615 --- /dev/null +++ b/nisystemlink/clients/system/__init__.py @@ -0,0 +1 @@ +from ._system_client import SystemClient diff --git a/nisystemlink/clients/systems/_systems_client.py b/nisystemlink/clients/system/_system_client.py similarity index 97% rename from nisystemlink/clients/systems/_systems_client.py rename to nisystemlink/clients/system/_system_client.py index 0859bda9..593b37b1 100644 --- a/nisystemlink/clients/systems/_systems_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -11,7 +11,7 @@ from . import models -class SystemsClient(BaseClient): +class SystemClient(BaseClient): def __init__(self, configuration: Optional[core.HttpConfiguration] = None): """Initialize an instance. @@ -22,7 +22,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): is used to obtain the configuration. Raises: - ApiException: if unable to communicate with the Systems Service. + ApiException: if unable to communicate with the System Service. """ if configuration is None: configuration = core.HttpConfigurationManager.get_configuration() @@ -63,7 +63,7 @@ def list_jobs( The list of jobs that matched the criteria. Raises: - ApiException: if unable to communicate with the Systems Service + ApiException: if unable to communicate with the System Service or provided an invalid argument. """ ... @@ -79,7 +79,7 @@ def create_job(self, job: models.CreateJobRequest) -> models.CreateJobResponse: The job that was created. Raises: - ApiException: if unable to communicate with the Systems Service + ApiException: if unable to communicate with the System Service or provided an invalid argument. """ ... @@ -92,7 +92,7 @@ def get_job_summary(self) -> models.JobSummaryResponse: An instance of a JobsSummaryResponse. Raises: - ApiException: if unable to communicate with the Systems Service + ApiException: if unable to communicate with the System Service or provided an invalid argument. """ ... @@ -108,7 +108,7 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse An instance of QueryJobsRequest. Raises: - ApiException: if unable to communicate with the Systems Service + ApiException: if unable to communicate with the System Service or provided an invalid argument. """ ... @@ -126,7 +126,7 @@ def cancel_jobs( The errors that appear while attempting the operation. Raises: - ApiException: if unable to communicate with the Systems Service + ApiException: if unable to communicate with the System Service or provided an invalid argument. """ ... diff --git a/nisystemlink/clients/systems/models/__init__.py b/nisystemlink/clients/system/models/__init__.py similarity index 100% rename from nisystemlink/clients/systems/models/__init__.py rename to nisystemlink/clients/system/models/__init__.py diff --git a/nisystemlink/clients/systems/models/_cancel_job_request.py b/nisystemlink/clients/system/models/_cancel_job_request.py similarity index 100% rename from nisystemlink/clients/systems/models/_cancel_job_request.py rename to nisystemlink/clients/system/models/_cancel_job_request.py diff --git a/nisystemlink/clients/systems/models/_cancel_job_response.py b/nisystemlink/clients/system/models/_cancel_job_response.py similarity index 100% rename from nisystemlink/clients/systems/models/_cancel_job_response.py rename to nisystemlink/clients/system/models/_cancel_job_response.py diff --git a/nisystemlink/clients/systems/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py similarity index 100% rename from nisystemlink/clients/systems/models/_create_job_request.py rename to nisystemlink/clients/system/models/_create_job_request.py diff --git a/nisystemlink/clients/systems/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py similarity index 100% rename from nisystemlink/clients/systems/models/_create_job_response.py rename to nisystemlink/clients/system/models/_create_job_response.py diff --git a/nisystemlink/clients/systems/models/_http_error.py b/nisystemlink/clients/system/models/_http_error.py similarity index 100% rename from nisystemlink/clients/systems/models/_http_error.py rename to nisystemlink/clients/system/models/_http_error.py diff --git a/nisystemlink/clients/systems/models/_job.py b/nisystemlink/clients/system/models/_job.py similarity index 100% rename from nisystemlink/clients/systems/models/_job.py rename to nisystemlink/clients/system/models/_job.py diff --git a/nisystemlink/clients/systems/models/_job_config.py b/nisystemlink/clients/system/models/_job_config.py similarity index 100% rename from nisystemlink/clients/systems/models/_job_config.py rename to nisystemlink/clients/system/models/_job_config.py diff --git a/nisystemlink/clients/systems/models/_job_state.py b/nisystemlink/clients/system/models/_job_state.py similarity index 100% rename from nisystemlink/clients/systems/models/_job_state.py rename to nisystemlink/clients/system/models/_job_state.py diff --git a/nisystemlink/clients/systems/models/_job_summary_response.py b/nisystemlink/clients/system/models/_job_summary_response.py similarity index 100% rename from nisystemlink/clients/systems/models/_job_summary_response.py rename to nisystemlink/clients/system/models/_job_summary_response.py diff --git a/nisystemlink/clients/systems/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py similarity index 100% rename from nisystemlink/clients/systems/models/_query_jobs_request.py rename to nisystemlink/clients/system/models/_query_jobs_request.py diff --git a/nisystemlink/clients/systems/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py similarity index 100% rename from nisystemlink/clients/systems/models/_query_jobs_response.py rename to nisystemlink/clients/system/models/_query_jobs_response.py diff --git a/nisystemlink/clients/systems/__init__.py b/nisystemlink/clients/systems/__init__.py deleted file mode 100644 index 6a580aa5..00000000 --- a/nisystemlink/clients/systems/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._systems_client import SystemsClient diff --git a/tests/integration/systems/test_systems_client.py b/tests/integration/system/test_system_client.py similarity index 90% rename from tests/integration/systems/test_systems_client.py rename to tests/integration/system/test_system_client.py index a524fb5a..cc33a3fd 100644 --- a/tests/integration/systems/test_systems_client.py +++ b/tests/integration/system/test_system_client.py @@ -2,8 +2,8 @@ from typing import List, Generator, Callable from nisystemlink.clients.core._http_configuration import HttpConfiguration -from nisystemlink.clients.systems import SystemsClient -from nisystemlink.clients.systems.models import ( +from nisystemlink.clients.system import SystemClient +from nisystemlink.clients.system.models import ( CreateJobResponse, CreateJobRequest, JobSummaryResponse, @@ -13,14 +13,14 @@ @pytest.fixture(scope="class") -def client(enterprise_config: HttpConfiguration) -> SystemsClient: - """Fixture to create an SystemsClient instance.""" - return SystemsClient(enterprise_config) +def client(enterprise_config: HttpConfiguration) -> SystemClient: + """Fixture to create an SystemClient instance.""" + return SystemClient(enterprise_config) @pytest.fixture(scope="class") def create_job( - client: SystemsClient, + client: SystemClient, ): """Fixture to create a job.""" responses: List[CreateJobResponse] = [] @@ -32,7 +32,7 @@ def _create_job(job: CreateJobRequest) -> CreateJobResponse: yield _create_job - from nisystemlink.clients.systems.models import CancelJobRequest + from nisystemlink.clients.system.models import CancelJobRequest job_requests = [ CancelJobRequest(jid=response.jid, tgt=response.tgt[0]) @@ -76,7 +76,7 @@ def create_multiple_jobs( @pytest.mark.integration @pytest.mark.enterprise -class TestSystemsClient: +class TestSystemClient: def test__create_job__succeeds( self, create_job, @@ -103,7 +103,7 @@ def test__create_job__succeeds( assert response.metadata == metadata assert response.fun == fun - def test__list_jobs__single_job__succeeds(self, create_job, client: SystemsClient): + def test__list_jobs__single_job__succeeds(self, create_job, client: SystemClient): arg = [["A description"]] tgt = [ "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" @@ -132,41 +132,41 @@ def test__list_jobs__single_job__succeeds(self, create_job, client: SystemsClien assert response_job.config.fun == fun def test__list_jobs__multiple_jobs__succeeds( - self, create_multiple_jobs, client: SystemsClient + self, create_multiple_jobs, client: SystemClient ): response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0]) assert response is not None assert len(response) == 2 def test__list_jobs__multiple_jobs_take_one__succeeds( - self, create_multiple_jobs, client: SystemsClient + self, create_multiple_jobs, client: SystemClient ): response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], take=1) assert response is not None assert len(response) == 1 def test__list_jobs__multiple_jobs_skip_one__succeeds( - self, create_multiple_jobs, client: SystemsClient + self, create_multiple_jobs, client: SystemClient ): response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], skip=1) assert response is not None assert len(response) == 1 - def test__list_jobs__Invalid_system_id__fails(self, client: SystemsClient): + def test__list_jobs__Invalid_system_id__fails(self, client: SystemClient): with pytest.raises(Exception): client.list_jobs(system_id="Invalid_system_id") - def test__list_jobs__Invalid_jid__fails(self, client: SystemsClient): + def test__list_jobs__Invalid_jid__fails(self, client: SystemClient): with pytest.raises(Exception): client.list_jobs(jid="Invalid_jid") - def test__get_job_summary__succeeds(self, client: SystemsClient): + def test__get_job_summary__succeeds(self, client: SystemClient): response = client.get_job_summary() assert response is not None assert isinstance(response, JobSummaryResponse) - def test__query_jobs__take_filter__succeeds(self, client: SystemsClient): + def test__query_jobs__take_filter__succeeds(self, client: SystemClient): query = QueryJobsRequest(take=1) response = client.query_jobs(query=query) @@ -176,7 +176,7 @@ def test__query_jobs__take_filter__succeeds(self, client: SystemsClient): assert isinstance(response.data, list) assert response.count == 1 - def test__query_jobs__config_fun_filter__succeeds(self, client: SystemsClient): + def test__query_jobs__config_fun_filter__succeeds(self, client: SystemClient): query = QueryJobsRequest( filter='config.fun.Contains("system.set_computer_desc")' ) @@ -189,7 +189,7 @@ def test__query_jobs__config_fun_filter__succeeds(self, client: SystemsClient): assert response.count > 0 def test__query_jobs__config_jid_filter__succeeds( - self, create_multiple_jobs, client: SystemsClient + self, create_multiple_jobs, client: SystemClient ): query = QueryJobsRequest(filter=f"jid={create_multiple_jobs[0].jid}") response = client.query_jobs(query=query) @@ -200,7 +200,7 @@ def test__query_jobs__config_jid_filter__succeeds( assert isinstance(response.data, list) assert response.count == 1 - def test__cancel_jobs__single_job__succeeds(self, client: SystemsClient): + def test__cancel_jobs__single_job__succeeds(self, client: SystemClient): arg = [["A description"]] tgt = [ "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" @@ -220,7 +220,7 @@ def test__cancel_jobs__single_job__succeeds(self, client: SystemsClient): assert cancel_response.error is None - def test__cancel_jobs__multiple_job__succeeds(self, client: SystemsClient): + def test__cancel_jobs__multiple_job__succeeds(self, client: SystemClient): arg_1 = [["A description"]] arg_2 = [["Another description"]] tgt = [ @@ -251,7 +251,7 @@ def test__cancel_jobs__multiple_job__succeeds(self, client: SystemsClient): assert cancel_response.error is None - def test__cancel_jobs__Invalid_jid__fails(self, client: SystemsClient): + def test__cancel_jobs__Invalid_jid__fails(self, client: SystemClient): cancel_job_request = CancelJobRequest(jid="Invalid_jid", tgt="Invalid_tgt") cancel_response = client.cancel_jobs([cancel_job_request]) From 63083f52a4c36c14cb57d2e54379a407947eb6c8 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Fri, 29 Nov 2024 09:49:50 +0530 Subject: [PATCH 09/23] feat: add example documentation for system client. Signed-off-by: Ganesh Nithin --- examples/system/system.py | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/system/system.py diff --git a/examples/system/system.py b/examples/system/system.py new file mode 100644 index 00000000..b348fa5d --- /dev/null +++ b/examples/system/system.py @@ -0,0 +1,57 @@ +from nisystemlink.clients.core import HttpConfiguration +from nisystemlink.clients.system import SystemClient +from nisystemlink.clients.system.models import ( + JobState, + CreateJobRequest, + QueryJobsRequest, + CancelJobRequest, +) + +# Setup the server configuration to point to your instance of SystemLink Enterprise +server_configuration = HttpConfiguration( + server_uri="https://yourserver.yourcompany.com", + api_key="YourAPIKeyGeneratedFromSystemLink", +) +client = SystemClient(configuration=server_configuration) + +# Get all jobs that have succeeded +jobs = client.list_jobs( + system_id="system_id", + jid="jid", + state=JobState.SUCCEEDED, + function="function", + skip=0, + take=10, +) + +# Create a job +arg = [["A description"]] +tgt = ["HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B"] +fun = ["system.set_computer_desc"] +metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} +job = CreateJobRequest( + arg=arg, + tgt=tgt, + fun=fun, + metadata=metadata, +) + +create_job_response = client.create_job(job) + +# Get job summary +job_summary = client.get_job_summary() + +# Query jobs +query_job = QueryJobsRequest( + skip=0, + take=1000, + filter="result.success.Contains(false)", + projection="new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)", + orderBy="createdTimestamp descending", +) +query_jobs_response = client.query_jobs(query_job) + + +# Cancel a job +cancel_job_request = CancelJobRequest(jid=create_job_response.jid, tgt=tgt[0]) +cancel_job_response = client.cancel_jobs([cancel_job_request]) From 5a4f0ae8c1635a96a91d7c435d45535a4fb0d2c1 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Fri, 29 Nov 2024 12:50:34 +0530 Subject: [PATCH 10/23] feat: add api references for system client API. Signed-off-by: Ganesh Nithin --- docs/api_reference.rst | 1 + docs/api_reference/system.rst | 18 ++++++++++++++++++ docs/getting_started.rst | 28 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 docs/api_reference/system.rst diff --git a/docs/api_reference.rst b/docs/api_reference.rst index e3bd991a..bf0e8ecd 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -14,6 +14,7 @@ API Reference api_reference/dataframe api_reference/spec api_reference/file + api_reference/system Indices and tables ------------------ diff --git a/docs/api_reference/system.rst b/docs/api_reference/system.rst new file mode 100644 index 00000000..ed6440ec --- /dev/null +++ b/docs/api_reference/system.rst @@ -0,0 +1,18 @@ +.. _api_tag_page: + +nisystemlink.clients.system +====================== + +.. autoclass:: nisystemlink.clients.system.SystemClient + :exclude-members: __init__ + + .. automethod:: __init__ + .. automethod:: list_jobs + .. automethod:: create_job + .. automethod:: get_job_summary + .. automethod:: query_jobs + .. automethod:: cancel_jobs + +.. automodule:: nisystemlink.clients.system.models + :members: + :imported-members: diff --git a/docs/getting_started.rst b/docs/getting_started.rst index ffdb564a..7d46fb9b 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -210,5 +210,33 @@ Examples Get the metadata of a File using its Id and download it. .. literalinclude:: ../examples/file/download_file.py + :language: python + :linenos: + + +System API +------- + +Overview +~~~~~~~~ + +The :class:`.SystemClient` class is the primary entry point of the System API. + +When constructing a :class:`.SystemClient`, you can pass an +:class:`.HttpConfiguration` (like one retrieved from the +:class:`.HttpConfigurationManager`), or let :class:`.SystemClient` use the +default connection. The default connection depends on your environment. + +With a :class:`.SystemClient` object, you can: + +* Create, get, query, and cancel jobs +* Get the summary of a jobs + +Examples +~~~~~~~~ + +Create, get, query, and cancel jobs + +.. literalinclude:: ../examples/system/system.py :language: python :linenos: \ No newline at end of file From 1e17b89323a2fa4b40e51a499de5250c2a1ff0ff Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Sun, 1 Dec 2024 21:49:24 +0530 Subject: [PATCH 11/23] refactor: format code using black & flake8. Signed-off-by: Ganesh Nithin --- examples/system/system.py | 4 +- nisystemlink/clients/system/__init__.py | 2 + nisystemlink/clients/system/_system_client.py | 4 +- .../clients/system/models/__init__.py | 2 + .../system/models/_cancel_job_request.py | 2 +- .../system/models/_cancel_job_response.py | 2 +- .../system/models/_create_job_request.py | 2 +- .../clients/system/models/_http_error.py | 19 +++++--- nisystemlink/clients/system/models/_job.py | 4 +- .../system/models/_query_jobs_request.py | 46 ++++++++++++------- .../integration/system/test_system_client.py | 8 ++-- 11 files changed, 59 insertions(+), 36 deletions(-) diff --git a/examples/system/system.py b/examples/system/system.py index b348fa5d..9bd2f602 100644 --- a/examples/system/system.py +++ b/examples/system/system.py @@ -1,10 +1,10 @@ from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.system import SystemClient from nisystemlink.clients.system.models import ( - JobState, + CancelJobRequest, CreateJobRequest, + JobState, QueryJobsRequest, - CancelJobRequest, ) # Setup the server configuration to point to your instance of SystemLink Enterprise diff --git a/nisystemlink/clients/system/__init__.py b/nisystemlink/clients/system/__init__.py index a4d97615..830608c6 100644 --- a/nisystemlink/clients/system/__init__.py +++ b/nisystemlink/clients/system/__init__.py @@ -1 +1,3 @@ from ._system_client import SystemClient + +# flake8: noqa diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index 593b37b1..a3b30348 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -1,5 +1,4 @@ -from typing import Optional, List -from uplink import Query +from typing import List, Optional from nisystemlink.clients import core from nisystemlink.clients.core._uplink._base_client import BaseClient @@ -7,6 +6,7 @@ get, post, ) +from uplink import Query from . import models diff --git a/nisystemlink/clients/system/models/__init__.py b/nisystemlink/clients/system/models/__init__.py index 28ac5464..6cb657f2 100644 --- a/nisystemlink/clients/system/models/__init__.py +++ b/nisystemlink/clients/system/models/__init__.py @@ -7,3 +7,5 @@ from ._query_jobs_response import QueryJobsResponse from ._cancel_job_request import CancelJobRequest from ._cancel_job_response import CancelJobResponse + +# flake8: noqa diff --git a/nisystemlink/clients/system/models/_cancel_job_request.py b/nisystemlink/clients/system/models/_cancel_job_request.py index 041d3de3..9310c42d 100644 --- a/nisystemlink/clients/system/models/_cancel_job_request.py +++ b/nisystemlink/clients/system/models/_cancel_job_request.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from nisystemlink.clients.core._uplink._json_model import JsonModel diff --git a/nisystemlink/clients/system/models/_cancel_job_response.py b/nisystemlink/clients/system/models/_cancel_job_response.py index e229cf00..086a872e 100644 --- a/nisystemlink/clients/system/models/_cancel_job_response.py +++ b/nisystemlink/clients/system/models/_cancel_job_response.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from nisystemlink.clients.core._uplink._json_model import JsonModel diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index 9dd5a282..bf88ae44 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, Any +from typing import Any, Dict, Optional from ._job_config import JobConfig diff --git a/nisystemlink/clients/system/models/_http_error.py b/nisystemlink/clients/system/models/_http_error.py index 4d29bff7..74443fe8 100644 --- a/nisystemlink/clients/system/models/_http_error.py +++ b/nisystemlink/clients/system/models/_http_error.py @@ -7,22 +7,29 @@ class HttpError(JsonModel): """Represents the standard error structure.""" name: Optional[str] = None - """Gets the fully-qualified name that identifies the error code, such as the output of M:NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.ToString.""" + """Gets the fully-qualified name that identifies the error code, such as the output of + M:NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.ToString.""" code: Optional[int] = None - """Gets the optional NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.NumericCode (not HTTP status code) that identifies the error code.""" + """Gets the optional NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.NumericCode + (not HTTP status code) that identifies the error code.""" message: Optional[str] = None - """Gets the formatted error message, which is the NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.Message with NationalInstruments.SystemLink.ServiceBase.HttpError.Args inserted into any placeholders.""" + """Gets the formatted error message, which is the NationalInstruments.SystemLink.ServiceBase.ErrorHandling + .ErrorCode.Message with NationalInstruments.SystemLink.ServiceBase.HttpError.Args inserted into + any placeholders.""" resourceType: Optional[str] = None - """Gets the type of resource associated with the error, if any. Typically only used for instances within NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" + """Gets the type of resource associated with the error, if any. Typically only used for instances within + NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" resourceId: Optional[str] = None - """Gets the ID of the resource associated with the error, if any. Typically only used for instances within NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" + """Gets the ID of the resource associated with the error, if any. Typically only used for instances within + NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" args: Optional[List[str]] = None """Gets the arguments that produced NationalInstruments.SystemLink.ServiceBase.HttpError.Message.""" innerErrors: Optional[List["HttpError"]] = None - """Gets any nested errors if this instance represents more than one error. Typically only set for instances related to NationalInstruments.SystemLink.ServiceBase.ErrorHandling.SkylineErrorCodes.OneOrMoreErrorsOccurred.""" + """Gets any nested errors if this instance represents more than one error. Typically only set for instances + related to NationalInstruments.SystemLink.ServiceBase.ErrorHandling.SkylineErrorCodes.OneOrMoreErrorsOccurred.""" diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index 35645dc5..2568d104 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -1,10 +1,10 @@ -from typing import Dict, List, Optional from datetime import datetime +from typing import Dict, List, Optional from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._job_state import JobState from ._job_config import JobConfig +from ._job_state import JobState class JobResult(JsonModel): diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index 9a9dec47..e7101a06 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -2,6 +2,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel + class QueryJobsRequest(JsonModel): """Model for query job request.""" @@ -13,9 +14,10 @@ class QueryJobsRequest(JsonModel): filter: Optional[str] = None """ - Gets or sets the filter criteria for jobs or systems. Consists of a string of queries composed using AND/OR operators. - String values and date strings need to be enclosed in double quotes. Parenthesis can be used around filters to better - define the order of operations. Filter syntax: '[property name][operator][operand] and [property name][operator][operand]' + Gets or sets the filter criteria for jobs or systems. Consists of a string of queries composed using + AND/OR operators.String values and date strings need to be enclosed in double quotes. Parenthesis + can be used around filters to better define the order of operations. + Filter syntax: '[property name][operator][operand] and [property name][operator][operand]' Operators: Equals operator '='. Example: 'x = y' @@ -26,37 +28,47 @@ class QueryJobsRequest(JsonModel): Less than or equal operator '<='. Example: 'x <= y' Logical AND operator 'and' or '&&'. Example: 'x and y' Logical OR operator 'or' or '||'. Example: 'x or y' - Contains operator '.Contains()', used to check if a list contains an element. Example: 'x.Contains(y)' - Not Contains operator '!.Contains()', used to check if a list does not contain an element. Example: '!x.Contains(y)' + Contains operator '.Contains()', used to check if a list contains an element. + Example: 'x.Contains(y)' + Not Contains operator '!.Contains()', used to check if a list does not contain an element. + Example: '!x.Contains(y)' Valid job properties that can be used in the filter: jid : String representing the ID of the job. id : String representing the ID of the system. - createdTimestamp: ISO-8601 formatted timestamp string specifying the date when the job was created. - lastUpdatedTimestamp: ISO-8601 formatted timestamp string specifying the last date the job was updated. - dispatchedTimestamp: ISO-8601 formatted timestamp string specifying the date when the job was actually sent to the system. + createdTimestamp: ISO-8601 formatted timestamp string specifying the date when the job + was created. + lastUpdatedTimestamp: ISO-8601 formatted timestamp string specifying the last date the + job was updated. + dispatchedTimestamp: ISO-8601 formatted timestamp string specifying the date when the + job was actually sent to the system. state: String representing the state of the job. metadata: Object containg the the metadata of job. Example: metadata.queued config.user: String representing the user who created the job. config.tgt: List of strings representing the targeted systems. Example: config.tgt.Contains("id") - config.fun: List of strings representing the functions to be executed within the job. Example: config.fun.Contains("nisysmgmt.set_blackout") - config.arg: An array of arrays of variable type elements that are arguments to the function specified by the "fun" property. Example: config.arg[0].Contains("test") - result.return: An array of objects representing return values for each executed function. Example: result.return[0].Contains("Success") - result.retcode: An array of integers representing code values for each executed function. Example: result.retcode - result.success: An array of booleans representing success values for each executed function. Example: result.success.Contains(false) + config.fun: List of strings representing the functions to be executed within the job. + Example: config.fun.Contains("nisysmgmt.set_blackout") + config.arg: An array of arrays of variable type elements that are arguments to the function specified + by the "fun" property. Example: config.arg[0].Contains("test") + result.return: An array of objects representing return values for each executed function. + Example: result.return[0].Contains("Success") + result.retcode: An array of integers representing code values for each executed function. + Example: result.retcode + result.success: An array of booleans representing success values for each executed function. + Example: result.success.Contains(false) """ projection: Optional[str] = None """ Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. - - Examples: - 'new(id,jid,state)' - 'new(id,jid,config.user as user)' - 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' + + Examples: - 'new(id,jid,state)' - 'new(id,jid,config.user as user)' - + 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' """ order_by: Optional[str] = None """ The order in which the jobs return. - + Example: createdTimestamp descending """ - diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index cc33a3fd..aa9fd4e1 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -1,14 +1,14 @@ -import pytest -from typing import List, Generator, Callable +from typing import List +import pytest from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.system import SystemClient from nisystemlink.clients.system.models import ( - CreateJobResponse, + CancelJobRequest, CreateJobRequest, + CreateJobResponse, JobSummaryResponse, QueryJobsRequest, - CancelJobRequest, ) From 13accdab0d486f42a4b42368fe1ea2c1cb592a64 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Tue, 3 Dec 2024 13:19:51 +0530 Subject: [PATCH 12/23] refactor: address system client PR comments. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/_system_client.py | 12 +++---- .../clients/system/models/__init__.py | 3 +- .../system/models/_cancel_job_response.py | 5 ++- .../system/models/_create_job_request.py | 15 ++++++-- .../system/models/_create_job_response.py | 4 +-- .../clients/system/models/_http_error.py | 35 ------------------- nisystemlink/clients/system/models/_job.py | 30 ++++++++++++++-- .../clients/system/models/_job_config.py | 19 ---------- .../clients/system/models/_job_state.py | 12 ------- .../system/models/_job_summary_response.py | 5 ++- .../system/models/_query_jobs_response.py | 7 ++-- .../integration/system/test_system_client.py | 5 +-- 12 files changed, 60 insertions(+), 92 deletions(-) delete mode 100644 nisystemlink/clients/system/models/_http_error.py delete mode 100644 nisystemlink/clients/system/models/_job_config.py delete mode 100644 nisystemlink/clients/system/models/_job_state.py diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index a3b30348..915e5873 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -22,7 +22,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): is used to obtain the configuration. Raises: - ApiException: if unable to communicate with the System Service. + ApiException: if unable to communicate with the ``/nisysmgmt`` Service. """ if configuration is None: configuration = core.HttpConfigurationManager.get_configuration() @@ -63,7 +63,7 @@ def list_jobs( The list of jobs that matched the criteria. Raises: - ApiException: if unable to communicate with the System Service + ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ ... @@ -79,7 +79,7 @@ def create_job(self, job: models.CreateJobRequest) -> models.CreateJobResponse: The job that was created. Raises: - ApiException: if unable to communicate with the System Service + ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ ... @@ -92,7 +92,7 @@ def get_job_summary(self) -> models.JobSummaryResponse: An instance of a JobsSummaryResponse. Raises: - ApiException: if unable to communicate with the System Service + ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ ... @@ -108,7 +108,7 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse An instance of QueryJobsRequest. Raises: - ApiException: if unable to communicate with the System Service + ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ ... @@ -126,7 +126,7 @@ def cancel_jobs( The errors that appear while attempting the operation. Raises: - ApiException: if unable to communicate with the System Service + ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ ... diff --git a/nisystemlink/clients/system/models/__init__.py b/nisystemlink/clients/system/models/__init__.py index 6cb657f2..8c999a2d 100644 --- a/nisystemlink/clients/system/models/__init__.py +++ b/nisystemlink/clients/system/models/__init__.py @@ -1,5 +1,4 @@ -from ._job_state import JobState -from ._job import Job +from ._job import Job, JobState from ._create_job_request import CreateJobRequest from ._create_job_response import CreateJobResponse from ._job_summary_response import JobSummaryResponse diff --git a/nisystemlink/clients/system/models/_cancel_job_response.py b/nisystemlink/clients/system/models/_cancel_job_response.py index 086a872e..76b15aef 100644 --- a/nisystemlink/clients/system/models/_cancel_job_response.py +++ b/nisystemlink/clients/system/models/_cancel_job_response.py @@ -1,12 +1,11 @@ from typing import Optional +from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._http_error import HttpError - class CancelJobResponse(JsonModel): """Model for response of a cancel job request.""" - error: Optional[HttpError] = None + error: Optional[ApiError] = None """Represents the standard error structure.""" diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index bf88ae44..742fd9b3 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -1,10 +1,19 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional -from ._job_config import JobConfig +from nisystemlink.clients.core._uplink._json_model import JsonModel -class CreateJobRequest(JobConfig): +class CreateJobRequest(JsonModel): """Model for create job request.""" + arg: Optional[List[List[str]]] = None + """Arguments of the salt functions.""" + + tgt: Optional[List[str]] = None + """The target systems for the job.""" + + fun: Optional[List[str]] = None + """Salt functions related to the job.""" + metadata: Optional[Dict[str, Any]] = None """The metadata associated with the job.""" diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index bc12a4a3..ee9f5147 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -1,13 +1,13 @@ from typing import Optional +from nisystemlink.clients.core import ApiError from ._create_job_request import CreateJobRequest -from ._http_error import HttpError class CreateJobResponse(CreateJobRequest): """Model for response of create job request.""" - error: Optional[HttpError] = None + error: Optional[ApiError] = None """Represents the standard error structure.""" jid: Optional[str] = None diff --git a/nisystemlink/clients/system/models/_http_error.py b/nisystemlink/clients/system/models/_http_error.py deleted file mode 100644 index 74443fe8..00000000 --- a/nisystemlink/clients/system/models/_http_error.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import List, Optional - -from nisystemlink.clients.core._uplink._json_model import JsonModel - - -class HttpError(JsonModel): - """Represents the standard error structure.""" - - name: Optional[str] = None - """Gets the fully-qualified name that identifies the error code, such as the output of - M:NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.ToString.""" - - code: Optional[int] = None - """Gets the optional NationalInstruments.SystemLink.ServiceBase.ErrorHandling.ErrorCode.NumericCode - (not HTTP status code) that identifies the error code.""" - - message: Optional[str] = None - """Gets the formatted error message, which is the NationalInstruments.SystemLink.ServiceBase.ErrorHandling - .ErrorCode.Message with NationalInstruments.SystemLink.ServiceBase.HttpError.Args inserted into - any placeholders.""" - - resourceType: Optional[str] = None - """Gets the type of resource associated with the error, if any. Typically only used for instances within - NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" - - resourceId: Optional[str] = None - """Gets the ID of the resource associated with the error, if any. Typically only used for instances within - NationalInstruments.SystemLink.ServiceBase.HttpError.InnerErrors.""" - - args: Optional[List[str]] = None - """Gets the arguments that produced NationalInstruments.SystemLink.ServiceBase.HttpError.Message.""" - - innerErrors: Optional[List["HttpError"]] = None - """Gets any nested errors if this instance represents more than one error. Typically only set for instances - related to NationalInstruments.SystemLink.ServiceBase.ErrorHandling.SkylineErrorCodes.OneOrMoreErrorsOccurred.""" diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index 2568d104..831d4e6f 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -1,10 +1,36 @@ from datetime import datetime +from enum import Enum from typing import Dict, List, Optional + from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._job_config import JobConfig -from ._job_state import JobState + +class JobState(Enum): + """The state of the job.""" + + SUCCEEDED = "SUCCEEDED" + OUTOFQUEUE = "OUTOFQUEUE" + INQUEUE = "INQUEUE" + INPROGRESS = "INPROGRESS" + CANCELED = "CANCELED" + FAILED = "FAILED" + + +class JobConfig(JsonModel): + """The configuration of the job.""" + + user: Optional[str] = None + """The user who created the job.""" + + tgt: Optional[List[str]] = None + """The target systems for the job.""" + + fun: Optional[List[str]] = None + """Salt functions related to the job.""" + + arg: Optional[List[List[str]]] = None + """Arguments of the salt functions.""" class JobResult(JsonModel): diff --git a/nisystemlink/clients/system/models/_job_config.py b/nisystemlink/clients/system/models/_job_config.py deleted file mode 100644 index 40e3136e..00000000 --- a/nisystemlink/clients/system/models/_job_config.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List, Optional - -from nisystemlink.clients.core._uplink._json_model import JsonModel - - -class JobConfig(JsonModel): - """The configuration of the job.""" - - user: Optional[str] = None - """The user who created the job.""" - - tgt: Optional[List[str]] = None - """The target systems for the job.""" - - fun: Optional[List[str]] = None - """Salt functions related to the job.""" - - arg: Optional[List[List[str]]] = None - """Arguments of the salt functions.""" diff --git a/nisystemlink/clients/system/models/_job_state.py b/nisystemlink/clients/system/models/_job_state.py deleted file mode 100644 index 30a55539..00000000 --- a/nisystemlink/clients/system/models/_job_state.py +++ /dev/null @@ -1,12 +0,0 @@ -from enum import Enum - - -class JobState(Enum): - """The state of the job.""" - - SUCCEEDED = "SUCCEEDED" - OUTOFQUEUE = "OUTOFQUEUE" - INQUEUE = "INQUEUE" - INPROGRESS = "INPROGRESS" - CANCELED = "CANCELED" - FAILED = "FAILED" diff --git a/nisystemlink/clients/system/models/_job_summary_response.py b/nisystemlink/clients/system/models/_job_summary_response.py index 6e9df509..f7463b03 100644 --- a/nisystemlink/clients/system/models/_job_summary_response.py +++ b/nisystemlink/clients/system/models/_job_summary_response.py @@ -1,14 +1,13 @@ from typing import Optional +from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._http_error import HttpError - class JobSummaryResponse(JsonModel): """Model for request of jobs summary response.""" - error: Optional[HttpError] = None + error: Optional[ApiError] = None """Represents the standard error structure.""" active_count: Optional[int] = None diff --git a/nisystemlink/clients/system/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py index 9d45c38f..16da20c9 100644 --- a/nisystemlink/clients/system/models/_query_jobs_response.py +++ b/nisystemlink/clients/system/models/_query_jobs_response.py @@ -1,17 +1,18 @@ from typing import List, Optional +from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._http_error import HttpError +from ._job import Job class QueryJobsResponse(JsonModel): """Model for response of a query request.""" - error: Optional[HttpError] = None + error: Optional[ApiError] = None """Represents the standard error structure.""" - data: Optional[List[str]] = None + data: Optional[List[Job]] = [] """The data returned by the query.""" count: Optional[int] = None diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index aa9fd4e1..99309c5a 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -1,6 +1,7 @@ from typing import List import pytest +from nisystemlink.clients.core import ApiException from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.system import SystemClient from nisystemlink.clients.system.models import ( @@ -153,11 +154,11 @@ def test__list_jobs__multiple_jobs_skip_one__succeeds( assert len(response) == 1 def test__list_jobs__Invalid_system_id__fails(self, client: SystemClient): - with pytest.raises(Exception): + with pytest.raises(ApiException): client.list_jobs(system_id="Invalid_system_id") def test__list_jobs__Invalid_jid__fails(self, client: SystemClient): - with pytest.raises(Exception): + with pytest.raises(ApiException): client.list_jobs(jid="Invalid_jid") def test__get_job_summary__succeeds(self, client: SystemClient): From 5dd2381ed14943b1ec089f52bd1a4ec134f0cec5 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Tue, 3 Dec 2024 16:16:01 +0530 Subject: [PATCH 13/23] refactor: modify system client tests. Signed-off-by: Ganesh Nithin --- .../integration/system/test_system_client.py | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index 99309c5a..e546368e 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -33,8 +33,6 @@ def _create_job(job: CreateJobRequest) -> CreateJobResponse: yield _create_job - from nisystemlink.clients.system.models import CancelJobRequest - job_requests = [ CancelJobRequest(jid=response.jid, tgt=response.tgt[0]) for response in responses @@ -78,7 +76,7 @@ def create_multiple_jobs( @pytest.mark.integration @pytest.mark.enterprise class TestSystemClient: - def test__create_job__succeeds( + def test__create_job__one_job_created_with_right_field_values( self, create_job, ): @@ -98,13 +96,16 @@ def test__create_job__succeeds( response = create_job(job) assert response is not None - assert response.jid is not None + assert response.jid is not "" assert response.arg == arg assert response.tgt == tgt assert response.metadata == metadata assert response.fun == fun + assert response.error is None - def test__list_jobs__single_job__succeeds(self, create_job, client: SystemClient): + def test__list_jobs__list_single_job_succeeds( + self, create_job, client: SystemClient + ): arg = [["A description"]] tgt = [ "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" @@ -132,76 +133,82 @@ def test__list_jobs__single_job__succeeds(self, create_job, client: SystemClient assert response_job.metadata == metadata assert response_job.config.fun == fun - def test__list_jobs__multiple_jobs__succeeds( + def test__list_jobs__list_multiple_jobs_succeeds( self, create_multiple_jobs, client: SystemClient ): response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0]) - assert response is not None assert len(response) == 2 - def test__list_jobs__multiple_jobs_take_one__succeeds( + def test__list_jobs__list_multiple_jobs_take_one_succeeds( self, create_multiple_jobs, client: SystemClient ): response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], take=1) - assert response is not None assert len(response) == 1 - def test__list_jobs__multiple_jobs_skip_one__succeeds( + def test__list_jobs__list_multiple_jobs_skip_one_succeeds( self, create_multiple_jobs, client: SystemClient ): response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], skip=1) - assert response is not None assert len(response) == 1 - def test__list_jobs__Invalid_system_id__fails(self, client: SystemClient): - with pytest.raises(ApiException): - client.list_jobs(system_id="Invalid_system_id") + def test__list_jobs__invalid_system_id(self, client: SystemClient): + response = client.list_jobs(system_id="Invalid_system_id") + assert response == [] - def test__list_jobs__Invalid_jid__fails(self, client: SystemClient): - with pytest.raises(ApiException): - client.list_jobs(jid="Invalid_jid") + def test__list_jobs__invalid_jid(self, client: SystemClient): + response = client.list_jobs(jid="Invalid_jid") + assert response == [] - def test__get_job_summary__succeeds(self, client: SystemClient): + def test__get_job_summary__returns_job_summary(self, client: SystemClient): response = client.get_job_summary() assert response is not None - assert isinstance(response, JobSummaryResponse) + assert response.active_count is not None + assert response.failed_count is not None + assert response.succeeded_count is not None + assert response.error is None - def test__query_jobs__take_filter__succeeds(self, client: SystemClient): + def test__query_jobs__take_one_job_succeeds(self, client: SystemClient): query = QueryJobsRequest(take=1) response = client.query_jobs(query=query) assert response is not None - assert response.count is not None assert response.data is not None - assert isinstance(response.data, list) - assert response.count == 1 + assert len(response.data) == response.count == 1 - def test__query_jobs__config_fun_filter__succeeds(self, client: SystemClient): + def test__query_jobs__filter_config_fun_succeeds(self, client: SystemClient): query = QueryJobsRequest( filter='config.fun.Contains("system.set_computer_desc")' ) response = client.query_jobs(query=query) assert response is not None - assert response.count is not None assert response.data is not None - assert isinstance(response.data, list) - assert response.count > 0 + assert len(response.data) == response.count > 0 - def test__query_jobs__config_jid_filter__succeeds( + def test__query_jobs__filter_config_fun_fails(self, client: SystemClient): + query = QueryJobsRequest( + filter='config.fun.Contains("system.set_computer_desc")' + ) + with pytest.raises(ApiException): + client.query_jobs(query=query) + + def test__query_jobs__filter_config_jid_succeeds( self, create_multiple_jobs, client: SystemClient ): query = QueryJobsRequest(filter=f"jid={create_multiple_jobs[0].jid}") response = client.query_jobs(query=query) assert response is not None - assert response.count is not None assert response.data is not None - assert isinstance(response.data, list) - assert response.count == 1 + assert len(response.data) == response.count == 1 + + def test__query_jobs__filter_config_jid_fails(self, client: SystemClient): + query = QueryJobsRequest(filter="jid=Invalid_jid") + with pytest.raises(ApiException): + client.query_jobs(query=query) - def test__cancel_jobs__single_job__succeeds(self, client: SystemClient): + def test__cancel_jobs__cancel_single_job_succeeds(self, client: SystemClient): arg = [["A description"]] tgt = [ "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" @@ -221,7 +228,7 @@ def test__cancel_jobs__single_job__succeeds(self, client: SystemClient): assert cancel_response.error is None - def test__cancel_jobs__multiple_job__succeeds(self, client: SystemClient): + def test__cancel_jobs__cancel_multiple_job_succeeds(self, client: SystemClient): arg_1 = [["A description"]] arg_2 = [["Another description"]] tgt = [ @@ -252,7 +259,7 @@ def test__cancel_jobs__multiple_job__succeeds(self, client: SystemClient): assert cancel_response.error is None - def test__cancel_jobs__Invalid_jid__fails(self, client: SystemClient): + def test__cancel_jobs__cancel_with_invalid_jid_fails(self, client: SystemClient): cancel_job_request = CancelJobRequest(jid="Invalid_jid", tgt="Invalid_tgt") cancel_response = client.cancel_jobs([cancel_job_request]) From 4a4b977dc9080c33c1604b51ecd2b9ac69bd5ed1 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Tue, 3 Dec 2024 18:08:38 +0530 Subject: [PATCH 14/23] refactor: fix lint issues in CI. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/models/_create_job_response.py | 1 + tests/integration/system/test_system_client.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index ee9f5147..f40a0695 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -1,6 +1,7 @@ from typing import Optional from nisystemlink.clients.core import ApiError + from ._create_job_request import CreateJobRequest diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index e546368e..58bd5c93 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -8,7 +8,6 @@ CancelJobRequest, CreateJobRequest, CreateJobResponse, - JobSummaryResponse, QueryJobsRequest, ) @@ -96,7 +95,7 @@ def test__create_job__one_job_created_with_right_field_values( response = create_job(job) assert response is not None - assert response.jid is not "" + assert response.jid != "" assert response.arg == arg assert response.tgt == tgt assert response.metadata == metadata @@ -184,6 +183,7 @@ def test__query_jobs__filter_config_fun_succeeds(self, client: SystemClient): assert response is not None assert response.data is not None + assert response.count is not None assert len(response.data) == response.count > 0 def test__query_jobs__filter_config_fun_fails(self, client: SystemClient): From 814f1fb8de74290d542ee0a7be61b7fe2cbb097b Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 4 Dec 2024 16:04:10 +0530 Subject: [PATCH 15/23] refactor: modify model names. Signed-off-by: Ganesh Nithin --- examples/system/{system.py => job.py} | 0 nisystemlink/clients/system/models/__init__.py | 2 +- .../clients/system/models/_cancel_job_response.py | 2 +- .../clients/system/models/_create_job_request.py | 8 ++++---- .../clients/system/models/_create_job_response.py | 2 +- nisystemlink/clients/system/models/_job.py | 14 +++++++------- .../clients/system/models/_job_summary_response.py | 8 ++++---- .../clients/system/models/_query_jobs_request.py | 2 +- .../clients/system/models/_query_jobs_response.py | 6 +++--- tests/integration/system/test_system_client.py | 4 ++-- 10 files changed, 24 insertions(+), 24 deletions(-) rename examples/system/{system.py => job.py} (100%) diff --git a/examples/system/system.py b/examples/system/job.py similarity index 100% rename from examples/system/system.py rename to examples/system/job.py diff --git a/nisystemlink/clients/system/models/__init__.py b/nisystemlink/clients/system/models/__init__.py index 8c999a2d..860e89cb 100644 --- a/nisystemlink/clients/system/models/__init__.py +++ b/nisystemlink/clients/system/models/__init__.py @@ -1,4 +1,4 @@ -from ._job import Job, JobState +from ._job import Job, JobState, JobConfig, JobResult from ._create_job_request import CreateJobRequest from ._create_job_response import CreateJobResponse from ._job_summary_response import JobSummaryResponse diff --git a/nisystemlink/clients/system/models/_cancel_job_response.py b/nisystemlink/clients/system/models/_cancel_job_response.py index 76b15aef..e2fb1bc6 100644 --- a/nisystemlink/clients/system/models/_cancel_job_response.py +++ b/nisystemlink/clients/system/models/_cancel_job_response.py @@ -7,5 +7,5 @@ class CancelJobResponse(JsonModel): """Model for response of a cancel job request.""" - error: Optional[ApiError] = None + error: ApiError """Represents the standard error structure.""" diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index 742fd9b3..c06317de 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -7,13 +7,13 @@ class CreateJobRequest(JsonModel): """Model for create job request.""" arg: Optional[List[List[str]]] = None - """Arguments of the salt functions.""" + """List of arguments to the functions specified in the "fun" property.""" tgt: Optional[List[str]] = None - """The target systems for the job.""" + """List of system IDs on which to run the job.""" fun: Optional[List[str]] = None - """Salt functions related to the job.""" + """Functions contained in the job.""" metadata: Optional[Dict[str, Any]] = None - """The metadata associated with the job.""" + """Additional information of the job.""" diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index f40a0695..def2e1cc 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -8,7 +8,7 @@ class CreateJobResponse(CreateJobRequest): """Model for response of create job request.""" - error: Optional[ApiError] = None + error: ApiError """Represents the standard error structure.""" jid: Optional[str] = None diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index 831d4e6f..30b4b73e 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import Enum -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -59,23 +59,23 @@ class Job(JsonModel): id: Optional[str] = None """The ID of the system that the job targets.""" - created_timestamp: Optional[datetime] = None + created_timestamp: datetime """The timestamp representing when the job was created.""" - last_updated_timestamp: Optional[datetime] = None + last_updated_timestamp: datetime """The timestamp representing when the job was last updated.""" dispatched_timestamp: Optional[datetime] = None """The timestamp representing when the job was dispatched.""" - state: Optional[JobState] = None + state: JobState """The state of the job.""" - metadata: Optional[Dict[str, str]] = None + metadata: Optional[Dict[str, Any]] = None """The metadata associated with the job.""" - config: Optional[JobConfig] = None + config: JobConfig """The configuration of the job.""" - result: Optional[JobResult] = None + result: JobResult """The result of the job.""" diff --git a/nisystemlink/clients/system/models/_job_summary_response.py b/nisystemlink/clients/system/models/_job_summary_response.py index f7463b03..848a2b70 100644 --- a/nisystemlink/clients/system/models/_job_summary_response.py +++ b/nisystemlink/clients/system/models/_job_summary_response.py @@ -7,14 +7,14 @@ class JobSummaryResponse(JsonModel): """Model for request of jobs summary response.""" - error: Optional[ApiError] = None + error: ApiError """Represents the standard error structure.""" - active_count: Optional[int] = None + active_count: int """The number of active jobs.""" - failed_count: Optional[int] = None + failed_count: int """The number of failed jobs.""" - succeeded_count: Optional[int] = None + succeeded_count: int """The number of succeeded jobs.""" diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index e7101a06..386a13c9 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -6,7 +6,7 @@ class QueryJobsRequest(JsonModel): """Model for query job request.""" - skip: Optional[int] = None + skip: int """The number of jobs to skip.""" take: Optional[int] = None diff --git a/nisystemlink/clients/system/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py index 16da20c9..4ed31a35 100644 --- a/nisystemlink/clients/system/models/_query_jobs_response.py +++ b/nisystemlink/clients/system/models/_query_jobs_response.py @@ -9,11 +9,11 @@ class QueryJobsResponse(JsonModel): """Model for response of a query request.""" - error: Optional[ApiError] = None + error: ApiError """Represents the standard error structure.""" - data: Optional[List[Job]] = [] + data: Optional[List[Job]] = None """The data returned by the query.""" - count: Optional[int] = None + count: int """The total number of resources that matched the query.""" diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index 58bd5c93..fa7887e3 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -152,11 +152,11 @@ def test__list_jobs__list_multiple_jobs_skip_one_succeeds( def test__list_jobs__invalid_system_id(self, client: SystemClient): response = client.list_jobs(system_id="Invalid_system_id") - assert response == [] + assert len(response) == 0 def test__list_jobs__invalid_jid(self, client: SystemClient): response = client.list_jobs(jid="Invalid_jid") - assert response == [] + assert len(response) == 0 def test__get_job_summary__returns_job_summary(self, client: SystemClient): response = client.get_job_summary() From 85cc1031938263fb8eb8d40344aefe1edb67047d Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 4 Dec 2024 22:20:03 +0530 Subject: [PATCH 16/23] refactor: modify test cases for system client. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/_system_client.py | 2 +- .../system/models/_create_job_response.py | 2 +- nisystemlink/clients/system/models/_job.py | 13 +- .../integration/system/test_system_client.py | 195 +++++++++--------- 4 files changed, 112 insertions(+), 100 deletions(-) diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index 915e5873..5478ddd3 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -116,7 +116,7 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse @post("cancel-jobs") def cancel_jobs( self, job_ids: List[models.CancelJobRequest] - ) -> models.CancelJobResponse: + ) -> models.CancelJobResponse | None: """Cancel the jobs. Args: diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index def2e1cc..f40a0695 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -8,7 +8,7 @@ class CreateJobResponse(CreateJobRequest): """Model for response of create job request.""" - error: ApiError + error: Optional[ApiError] = None """Represents the standard error structure.""" jid: Optional[str] = None diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index 30b4b73e..e3df486f 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -59,23 +59,26 @@ class Job(JsonModel): id: Optional[str] = None """The ID of the system that the job targets.""" - created_timestamp: datetime + created_timestamp: Optional[datetime] = None """The timestamp representing when the job was created.""" - last_updated_timestamp: datetime + last_updated_timestamp: Optional[datetime] = None """The timestamp representing when the job was last updated.""" dispatched_timestamp: Optional[datetime] = None """The timestamp representing when the job was dispatched.""" - state: JobState + state: Optional[JobState] = None """The state of the job.""" metadata: Optional[Dict[str, Any]] = None """The metadata associated with the job.""" - config: JobConfig + config: Optional[JobConfig] = None """The configuration of the job.""" - result: JobResult + result: Optional[JobResult] = None """The result of the job.""" + + class Config: + orm_mode = True diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index fa7887e3..9fdd3897 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -11,6 +11,13 @@ QueryJobsRequest, ) +TARGET_SYSTEM = ( + "HVM_domU--SN-ec200972-eeca-062e-5bf5-33g3g3g3d73b2--MAC-0A-E1-20-D6-96-2B" +) +METADATA = {"queued": True, "refresh_minion_cache": {"grains": True}} +SAMPLE_FUN_1 = "system.sample_function_one" +SAMPLE_FUN_2 = "system.sample_function_two" + @pytest.fixture(scope="class") def client(enterprise_config: HttpConfiguration) -> SystemClient: @@ -33,26 +40,29 @@ def _create_job(job: CreateJobRequest) -> CreateJobResponse: yield _create_job job_requests = [ - CancelJobRequest(jid=response.jid, tgt=response.tgt[0]) + CancelJobRequest(jid=response.jid, system_id=TARGET_SYSTEM) for response in responses - if response.tgt is not None + if response.jid != "" ] - client.cancel_jobs(job_requests) + if len(job_requests): + client.cancel_jobs(job_requests) -@pytest.fixture(scope="class") +@pytest.fixture(scope="class", autouse=True) def create_multiple_jobs( create_job, ): """Fixture to create multiple jobs.""" responses = [] - arg_1 = [["A description"]] - arg_2 = [["Another description"]] - tgt = ["HVM_domU--SN-ec200972-eeca-062e-5bf5-33g3g3g3d73b2--MAC-0A-E1-20-D6-96-2B"] - fun_1 = ["system.set_computer_desc"] - fun_2 = ["system.set_computer_asc"] - metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + arg_1 = [["sample argument one"]] + arg_2 = [["sample argument two"]] + tgt = [TARGET_SYSTEM] + fun_1 = [SAMPLE_FUN_1] + fun_2 = [SAMPLE_FUN_2] + + metadata = METADATA + job_1 = CreateJobRequest( arg=arg_1, tgt=tgt, @@ -69,92 +79,87 @@ def create_multiple_jobs( ) responses.append(create_job(job_2)) + job_3 = CreateJobRequest( + arg=arg_1, + tgt=tgt, + fun=fun_2, + metadata=metadata, + ) + responses.append(create_job(job_3)) + return responses @pytest.mark.integration @pytest.mark.enterprise class TestSystemClient: - def test__create_job__one_job_created_with_right_field_values( + def test__create_a_job__job_is_created_with_right_field_values( self, create_job, ): - arg = [["A description"]] - tgt = [ - "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" - ] - fun = ["system.set_computer_desc"] - metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + arg = [["sample argument"]] + tgt = [TARGET_SYSTEM] + fun = ["system.set_computer_desc_sample_function"] + job = CreateJobRequest( arg=arg, tgt=tgt, fun=fun, - metadata=metadata, + metadata=METADATA, ) response = create_job(job) assert response is not None assert response.jid != "" - assert response.arg == arg assert response.tgt == tgt - assert response.metadata == metadata - assert response.fun == fun assert response.error is None - def test__list_jobs__list_single_job_succeeds( - self, create_job, client: SystemClient + def test__get_job_using_target_and_job_id__returns_job_matches_target_and_job_id( + self, create_multiple_jobs, client: SystemClient ): - arg = [["A description"]] - tgt = [ - "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" - ] - fun = ["system.set_computer_desc"] - metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} - job = CreateJobRequest( - arg=arg, - tgt=tgt, - fun=fun, - metadata=metadata, - ) - create_job_response = create_job(job) - - response = client.list_jobs(jid=create_job_response.jid) - assert response is not None + [first_job, *_] = create_multiple_jobs + print(first_job.jid) + response = client.list_jobs(system_id=TARGET_SYSTEM, jid=first_job.jid) assert len(response) == 1 - [response_job] = response - - assert response_job.jid == create_job_response.jid + print(response) + assert response_job.jid == first_job.jid assert response_job.config is not None - assert response_job.config.arg == arg - assert response_job.config.tgt == tgt - assert response_job.metadata == metadata - assert response_job.config.fun == fun + assert response_job.config.tgt == first_job.tgt - def test__list_jobs__list_multiple_jobs_succeeds( + def test__get_jobs_using_target_and_function__return_jobs_match_target_and_function( self, create_multiple_jobs, client: SystemClient ): - response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0]) + [_, second_job, third_job] = create_multiple_jobs + response = client.list_jobs(system_id=TARGET_SYSTEM, function=SAMPLE_FUN_2) assert len(response) == 2 - def test__list_jobs__list_multiple_jobs_take_one_succeeds( - self, create_multiple_jobs, client: SystemClient - ): - response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], take=1) - assert len(response) == 1 + [response_second_job, response_third_job] = response + + assert response_second_job.jid == second_job.jid + assert response_second_job.config is not None + assert response_second_job.config.tgt == second_job.tgt - def test__list_jobs__list_multiple_jobs_skip_one_succeeds( + assert response_third_job.jid == third_job.jid + assert response_third_job.config is not None + assert response_third_job.config.tgt == third_job.tgt + + def test__get_job_by_taking_one__return_only_one_job( self, create_multiple_jobs, client: SystemClient ): - response = client.list_jobs(system_id=create_multiple_jobs[0].tgt[0], skip=1) + response = client.list_jobs(system_id=TARGET_SYSTEM, skip=1) assert len(response) == 1 - def test__list_jobs__invalid_system_id(self, client: SystemClient): + def test__get_jobs_using_invalid_system_id__returns_empty_list( + self, client: SystemClient + ): response = client.list_jobs(system_id="Invalid_system_id") assert len(response) == 0 - def test__list_jobs__invalid_jid(self, client: SystemClient): + def test__get_jobs_using_invalid_jid__returns_empty_list( + self, client: SystemClient + ): response = client.list_jobs(jid="Invalid_jid") assert len(response) == 0 @@ -167,18 +172,18 @@ def test__get_job_summary__returns_job_summary(self, client: SystemClient): assert response.succeeded_count is not None assert response.error is None - def test__query_jobs__take_one_job_succeeds(self, client: SystemClient): - query = QueryJobsRequest(take=1) + def test__query_jobs_by_taking_one__returns_one_job(self, client: SystemClient): + query = QueryJobsRequest(skip=0, take=1) response = client.query_jobs(query=query) assert response is not None assert response.data is not None assert len(response.data) == response.count == 1 - def test__query_jobs__filter_config_fun_succeeds(self, client: SystemClient): - query = QueryJobsRequest( - filter='config.fun.Contains("system.set_computer_desc")' - ) + def test__query_jobs_by_filtering_config__return_jobs_matches_filter( + self, client: SystemClient + ): + query = QueryJobsRequest(skip=0, filter=f"config.fun.Contains({SAMPLE_FUN_2})") response = client.query_jobs(query=query) assert response is not None @@ -186,82 +191,86 @@ def test__query_jobs__filter_config_fun_succeeds(self, client: SystemClient): assert response.count is not None assert len(response.data) == response.count > 0 - def test__query_jobs__filter_config_fun_fails(self, client: SystemClient): + def test__query_jobs_by_filtering_invalid_filter__raises_ApiException( + self, client: SystemClient + ): query = QueryJobsRequest( - filter='config.fun.Contains("system.set_computer_desc")' + skip=0, filter='config.fun.Contains("failed_function")' ) with pytest.raises(ApiException): client.query_jobs(query=query) - def test__query_jobs__filter_config_jid_succeeds( + def test__query_jobs_by_filtering_jid__returns_job_matches_jid( self, create_multiple_jobs, client: SystemClient ): - query = QueryJobsRequest(filter=f"jid={create_multiple_jobs[0].jid}") + query = QueryJobsRequest(skip=0, filter=f"jid={create_multiple_jobs[0].jid}") response = client.query_jobs(query=query) assert response is not None assert response.data is not None assert len(response.data) == response.count == 1 - def test__query_jobs__filter_config_jid_fails(self, client: SystemClient): - query = QueryJobsRequest(filter="jid=Invalid_jid") + def test__query_jobs_by_filtering_invalid_jid__raises_ApiException( + self, client: SystemClient + ): + query = QueryJobsRequest(skip=0, filter="jid=Invalid_jid") with pytest.raises(ApiException): client.query_jobs(query=query) - def test__cancel_jobs__cancel_single_job_succeeds(self, client: SystemClient): - arg = [["A description"]] - tgt = [ - "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" - ] - fun = ["system.set_computer_desc"] - metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} + def test__cancel_single_job__cancel_single_job_succeeds(self, client: SystemClient): + arg = [["sample argument"]] + tgt = [TARGET_SYSTEM] + fun = [SAMPLE_FUN_1] job = CreateJobRequest( arg=arg, tgt=tgt, fun=fun, - metadata=metadata, + metadata=METADATA, ) response = client.create_job(job) - cancel_job_request = CancelJobRequest(jid=response.jid, tgt=tgt[0]) + cancel_job_request = CancelJobRequest(jid=response.jid, tgt=TARGET_SYSTEM) cancel_response = client.cancel_jobs([cancel_job_request]) - assert cancel_response.error is None + assert cancel_response is None + + def test__cancel_multiple_jobs__cancel_multiple_job_succeeds( + self, client: SystemClient + ): + arg_1 = [["sample argument one"]] + arg_2 = [["sample argument two"]] + tgt = [TARGET_SYSTEM] + fun = [SAMPLE_FUN_2] - def test__cancel_jobs__cancel_multiple_job_succeeds(self, client: SystemClient): - arg_1 = [["A description"]] - arg_2 = [["Another description"]] - tgt = [ - "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" - ] - fun = ["system.set_computer_desc"] - metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} job_1 = CreateJobRequest( arg=arg_1, tgt=tgt, fun=fun, - metadata=metadata, + metadata=METADATA, ) response_1 = client.create_job(job_1) job_2 = CreateJobRequest( arg=arg_2, tgt=tgt, fun=fun, - metadata=metadata, + metadata=METADATA, ) response_2 = client.create_job(job_2) - cancel_job_request_1 = CancelJobRequest(jid=response_1.jid, tgt=tgt[0]) - cancel_job_request_2 = CancelJobRequest(jid=response_2.jid, tgt=tgt[0]) + cancel_job_request_1 = CancelJobRequest(jid=response_1.jid, tgt=TARGET_SYSTEM) + cancel_job_request_2 = CancelJobRequest(jid=response_2.jid, tgt=TARGET_SYSTEM) cancel_response = client.cancel_jobs( [cancel_job_request_1, cancel_job_request_2] ) - assert cancel_response.error is None + assert cancel_response is None - def test__cancel_jobs__cancel_with_invalid_jid_fails(self, client: SystemClient): + def test__cancel_with_invalid_jid__cancel_job_returns_error( + self, client: SystemClient + ): cancel_job_request = CancelJobRequest(jid="Invalid_jid", tgt="Invalid_tgt") cancel_response = client.cancel_jobs([cancel_job_request]) + assert cancel_response is not None assert cancel_response.error is not None assert cancel_response.error.message is not None From 3ef21ec872a8fd4e01d87252c31dc92419c923dc Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Fri, 6 Dec 2024 19:24:30 +0530 Subject: [PATCH 17/23] refactor: modify field names to be pythonic. Signed-off-by: Ganesh Nithin --- examples/system/job.py | 8 +- .../system/models/_cancel_job_request.py | 3 +- .../system/models/_create_job_request.py | 7 +- .../system/models/_create_job_response.py | 3 +- nisystemlink/clients/system/models/_job.py | 19 ++--- .../integration/system/test_system_client.py | 73 ++++++++++--------- 6 files changed, 59 insertions(+), 54 deletions(-) diff --git a/examples/system/job.py b/examples/system/job.py index 9bd2f602..5fe0283e 100644 --- a/examples/system/job.py +++ b/examples/system/job.py @@ -30,9 +30,9 @@ fun = ["system.set_computer_desc"] metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} job = CreateJobRequest( - arg=arg, - tgt=tgt, - fun=fun, + arguments=arg, + target_systems=tgt, + functions=fun, metadata=metadata, ) @@ -53,5 +53,5 @@ # Cancel a job -cancel_job_request = CancelJobRequest(jid=create_job_response.jid, tgt=tgt[0]) +cancel_job_request = CancelJobRequest(id=create_job_response.id, tgt=tgt[0]) cancel_job_response = client.cancel_jobs([cancel_job_request]) diff --git a/nisystemlink/clients/system/models/_cancel_job_request.py b/nisystemlink/clients/system/models/_cancel_job_request.py index 9310c42d..d8400269 100644 --- a/nisystemlink/clients/system/models/_cancel_job_request.py +++ b/nisystemlink/clients/system/models/_cancel_job_request.py @@ -1,3 +1,4 @@ +from pydantic import Field from typing import Optional from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -6,7 +7,7 @@ class CancelJobRequest(JsonModel): """Model for cancel job request.""" - jid: Optional[str] = None + id: Optional[str] = Field(None, alias="jid") """The ID of the job to cancel.""" system_id: Optional[str] = None diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index c06317de..bf9d4249 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -1,4 +1,5 @@ from typing import Any, Dict, List, Optional +from pydantic import Field from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -6,13 +7,13 @@ class CreateJobRequest(JsonModel): """Model for create job request.""" - arg: Optional[List[List[str]]] = None + arguments: Optional[List[List[Any]]] = Field(None, alias="args") """List of arguments to the functions specified in the "fun" property.""" - tgt: Optional[List[str]] = None + target_systems: Optional[List[str]] = Field(None, alias="tgt") """List of system IDs on which to run the job.""" - fun: Optional[List[str]] = None + functions: Optional[List[str]] = Field(None, alias="fun") """Functions contained in the job.""" metadata: Optional[Dict[str, Any]] = None diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index f40a0695..589e4761 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -1,3 +1,4 @@ +from pydantic import Field from typing import Optional from nisystemlink.clients.core import ApiError @@ -11,5 +12,5 @@ class CreateJobResponse(CreateJobRequest): error: Optional[ApiError] = None """Represents the standard error structure.""" - jid: Optional[str] = None + id: Optional[str] = Field(None, alias="jid") """The job ID.""" diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index e3df486f..d54a5128 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -1,3 +1,4 @@ +from pydantic import Field from datetime import datetime from enum import Enum from typing import Any, Dict, List, Optional @@ -23,27 +24,27 @@ class JobConfig(JsonModel): user: Optional[str] = None """The user who created the job.""" - tgt: Optional[List[str]] = None + target_systems: Optional[List[str]] = Field(None, alias="tgt") """The target systems for the job.""" - fun: Optional[List[str]] = None + functions: Optional[List[str]] = Field(None, alias="fun") """Salt functions related to the job.""" - arg: Optional[List[List[str]]] = None + arguments: Optional[List[List[Any]]] = Field(None, alias="args") """Arguments of the salt functions.""" class JobResult(JsonModel): - jid: Optional[str] = None + id: Optional[str] = Field(None, alias="jid") """The job ID.""" - id: Optional[str] = None + system_id: Optional[str] = Field(None, alias="id") """The ID of the system that the job targets.""" - retcode: Optional[List[int]] = None + return_code: Optional[List[int]] = None """Return code of the job.""" - return_: Optional[List[str]] = None + return_: Optional[List[Any]] = None """Return value of the job.""" success: Optional[bool] = None @@ -53,10 +54,10 @@ class JobResult(JsonModel): class Job(JsonModel): """Job Model.""" - jid: Optional[str] = None + id: Optional[str] = Field(None, alias="jid") """The job ID.""" - id: Optional[str] = None + system_id: Optional[str] = Field(None, alias="id") """The ID of the system that the job targets.""" created_timestamp: Optional[datetime] = None diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index 9fdd3897..1d615115 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -12,7 +12,8 @@ ) TARGET_SYSTEM = ( - "HVM_domU--SN-ec200972-eeca-062e-5bf5-33g3g3g3d73b2--MAC-0A-E1-20-D6-96-2B" + # "HVM_domU--SN-ec200972-eeca-062e-5bf5-33g3g3g3d73b2--MAC-0A-E1-20-D6-96-2B" + "20UAS1L61D--SN-PF2K5S1M--MAC-8C-8C-AA-82-6A-F8" ) METADATA = {"queued": True, "refresh_minion_cache": {"grains": True}} SAMPLE_FUN_1 = "system.sample_function_one" @@ -40,9 +41,9 @@ def _create_job(job: CreateJobRequest) -> CreateJobResponse: yield _create_job job_requests = [ - CancelJobRequest(jid=response.jid, system_id=TARGET_SYSTEM) + CancelJobRequest(id=response.id, system_id=TARGET_SYSTEM) for response in responses - if response.jid != "" + if response.id != "" ] if len(job_requests): @@ -64,25 +65,25 @@ def create_multiple_jobs( metadata = METADATA job_1 = CreateJobRequest( - arg=arg_1, - tgt=tgt, - fun=fun_1, + arguments=arg_1, + target_systems=tgt, + functions=fun_1, metadata=metadata, ) responses.append(create_job(job_1)) job_2 = CreateJobRequest( - arg=arg_2, - tgt=tgt, - fun=fun_2, + arguments=arg_2, + target_systems=tgt, + functions=fun_2, metadata=metadata, ) responses.append(create_job(job_2)) job_3 = CreateJobRequest( - arg=arg_1, - tgt=tgt, - fun=fun_2, + arguments=arg_1, + target_systems=tgt, + functions=fun_2, metadata=metadata, ) responses.append(create_job(job_3)) @@ -102,17 +103,17 @@ def test__create_a_job__job_is_created_with_right_field_values( fun = ["system.set_computer_desc_sample_function"] job = CreateJobRequest( - arg=arg, - tgt=tgt, - fun=fun, + arguments=arg, + target_systems=tgt, + functions=fun, metadata=METADATA, ) response = create_job(job) assert response is not None - assert response.jid != "" - assert response.tgt == tgt + assert response.id != "" + assert response.target_systems == tgt assert response.error is None def test__get_job_using_target_and_job_id__returns_job_matches_target_and_job_id( @@ -124,9 +125,9 @@ def test__get_job_using_target_and_job_id__returns_job_matches_target_and_job_id assert len(response) == 1 [response_job] = response print(response) - assert response_job.jid == first_job.jid + assert response_job.id == first_job.jid assert response_job.config is not None - assert response_job.config.tgt == first_job.tgt + assert response_job.config.target_systems == first_job.tgt def test__get_jobs_using_target_and_function__return_jobs_match_target_and_function( self, create_multiple_jobs, client: SystemClient @@ -137,13 +138,13 @@ def test__get_jobs_using_target_and_function__return_jobs_match_target_and_funct [response_second_job, response_third_job] = response - assert response_second_job.jid == second_job.jid + assert response_second_job.id == second_job.jid assert response_second_job.config is not None - assert response_second_job.config.tgt == second_job.tgt + assert response_second_job.config.target_systems == second_job.tgt - assert response_third_job.jid == third_job.jid + assert response_third_job.id == third_job.jid assert response_third_job.config is not None - assert response_third_job.config.tgt == third_job.tgt + assert response_third_job.config.target_systems == third_job.tgt def test__get_job_by_taking_one__return_only_one_job( self, create_multiple_jobs, client: SystemClient @@ -222,14 +223,14 @@ def test__cancel_single_job__cancel_single_job_succeeds(self, client: SystemClie tgt = [TARGET_SYSTEM] fun = [SAMPLE_FUN_1] job = CreateJobRequest( - arg=arg, - tgt=tgt, - fun=fun, + arguments=arg, + target_systems=tgt, + functions=fun, metadata=METADATA, ) response = client.create_job(job) - cancel_job_request = CancelJobRequest(jid=response.jid, tgt=TARGET_SYSTEM) + cancel_job_request = CancelJobRequest(id=response.id, tgt=TARGET_SYSTEM) cancel_response = client.cancel_jobs([cancel_job_request]) assert cancel_response is None @@ -243,22 +244,22 @@ def test__cancel_multiple_jobs__cancel_multiple_job_succeeds( fun = [SAMPLE_FUN_2] job_1 = CreateJobRequest( - arg=arg_1, - tgt=tgt, - fun=fun, + arguments=arg_1, + target_systems=tgt, + functions=fun, metadata=METADATA, ) response_1 = client.create_job(job_1) job_2 = CreateJobRequest( - arg=arg_2, - tgt=tgt, - fun=fun, + arguments=arg_2, + target_systems=tgt, + functions=fun, metadata=METADATA, ) response_2 = client.create_job(job_2) - cancel_job_request_1 = CancelJobRequest(jid=response_1.jid, tgt=TARGET_SYSTEM) - cancel_job_request_2 = CancelJobRequest(jid=response_2.jid, tgt=TARGET_SYSTEM) + cancel_job_request_1 = CancelJobRequest(id=response_1.id, tgt=TARGET_SYSTEM) + cancel_job_request_2 = CancelJobRequest(id=response_2.id, tgt=TARGET_SYSTEM) cancel_response = client.cancel_jobs( [cancel_job_request_1, cancel_job_request_2] ) @@ -268,7 +269,7 @@ def test__cancel_multiple_jobs__cancel_multiple_job_succeeds( def test__cancel_with_invalid_jid__cancel_job_returns_error( self, client: SystemClient ): - cancel_job_request = CancelJobRequest(jid="Invalid_jid", tgt="Invalid_tgt") + cancel_job_request = CancelJobRequest(id="Invalid_jid", tgt="Invalid_tgt") cancel_response = client.cancel_jobs([cancel_job_request]) assert cancel_response is not None From 6167da2e5beff5da8f47ee73d29ceb1432011fdf Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Mon, 9 Dec 2024 09:03:52 +0530 Subject: [PATCH 18/23] refactor: modify tests. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/_system_client.py | 22 ++++++++++++++----- .../clients/system/models/__init__.py | 1 - .../system/models/_cancel_job_request.py | 4 ++-- .../system/models/_cancel_job_response.py | 11 ---------- .../system/models/_create_job_request.py | 4 ++-- .../system/models/_create_job_response.py | 2 +- nisystemlink/clients/system/models/_job.py | 20 ++++++++--------- .../system/models/_job_summary_response.py | 2 +- .../system/models/_query_jobs_request.py | 4 +++- .../system/models/_query_jobs_response.py | 2 +- .../integration/system/test_system_client.py | 16 +++++++++----- 11 files changed, 47 insertions(+), 41 deletions(-) delete mode 100644 nisystemlink/clients/system/models/_cancel_job_response.py diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index 5478ddd3..46179ee7 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -1,16 +1,25 @@ -from typing import List, Optional +from typing import List, Optional, Union from nisystemlink.clients import core +from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._base_client import BaseClient -from nisystemlink.clients.core._uplink._methods import ( - get, - post, -) +from nisystemlink.clients.core._uplink._methods import get, post, response_handler from uplink import Query +from requests.models import Response from . import models +def _cancel_job_response_handler(response: Response) -> Union[ApiError, None]: + """Response handler for Cancel Job response.""" + if response is None: + return None + + cancel_response = response.json() + + return cancel_response.get("error") + + class SystemClient(BaseClient): def __init__(self, configuration: Optional[core.HttpConfiguration] = None): """Initialize an instance. @@ -113,10 +122,11 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse """ ... + @response_handler(_cancel_job_response_handler) @post("cancel-jobs") def cancel_jobs( self, job_ids: List[models.CancelJobRequest] - ) -> models.CancelJobResponse | None: + ) -> Union[ApiError, None]: """Cancel the jobs. Args: diff --git a/nisystemlink/clients/system/models/__init__.py b/nisystemlink/clients/system/models/__init__.py index 860e89cb..f6c1e59e 100644 --- a/nisystemlink/clients/system/models/__init__.py +++ b/nisystemlink/clients/system/models/__init__.py @@ -5,6 +5,5 @@ from ._query_jobs_request import QueryJobsRequest from ._query_jobs_response import QueryJobsResponse from ._cancel_job_request import CancelJobRequest -from ._cancel_job_response import CancelJobResponse # flake8: noqa diff --git a/nisystemlink/clients/system/models/_cancel_job_request.py b/nisystemlink/clients/system/models/_cancel_job_request.py index d8400269..36c91e8f 100644 --- a/nisystemlink/clients/system/models/_cancel_job_request.py +++ b/nisystemlink/clients/system/models/_cancel_job_request.py @@ -7,8 +7,8 @@ class CancelJobRequest(JsonModel): """Model for cancel job request.""" - id: Optional[str] = Field(None, alias="jid") + id: str = Field(alias="jid") """The ID of the job to cancel.""" - system_id: Optional[str] = None + system_id: str """The system ID that the job to cancel targets.""" diff --git a/nisystemlink/clients/system/models/_cancel_job_response.py b/nisystemlink/clients/system/models/_cancel_job_response.py deleted file mode 100644 index e2fb1bc6..00000000 --- a/nisystemlink/clients/system/models/_cancel_job_response.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Optional - -from nisystemlink.clients.core import ApiError -from nisystemlink.clients.core._uplink._json_model import JsonModel - - -class CancelJobResponse(JsonModel): - """Model for response of a cancel job request.""" - - error: ApiError - """Represents the standard error structure.""" diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index bf9d4249..47be26d7 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -10,10 +10,10 @@ class CreateJobRequest(JsonModel): arguments: Optional[List[List[Any]]] = Field(None, alias="args") """List of arguments to the functions specified in the "fun" property.""" - target_systems: Optional[List[str]] = Field(None, alias="tgt") + target_systems: List[str] = Field(alias="tgt") """List of system IDs on which to run the job.""" - functions: Optional[List[str]] = Field(None, alias="fun") + functions: List[str] = Field(alias="fun") """Functions contained in the job.""" metadata: Optional[Dict[str, Any]] = None diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index 589e4761..7f7db5b5 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -12,5 +12,5 @@ class CreateJobResponse(CreateJobRequest): error: Optional[ApiError] = None """Represents the standard error structure.""" - id: Optional[str] = Field(None, alias="jid") + id: str = Field(alias="jid") """The job ID.""" diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index d54a5128..238982c4 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -21,13 +21,13 @@ class JobState(Enum): class JobConfig(JsonModel): """The configuration of the job.""" - user: Optional[str] = None + user: str """The user who created the job.""" - target_systems: Optional[List[str]] = Field(None, alias="tgt") + target_systems: List[str] = Field(alias="tgt") """The target systems for the job.""" - functions: Optional[List[str]] = Field(None, alias="fun") + functions: List[str] = Field(alias="fun") """Salt functions related to the job.""" arguments: Optional[List[List[Any]]] = Field(None, alias="args") @@ -54,28 +54,28 @@ class JobResult(JsonModel): class Job(JsonModel): """Job Model.""" - id: Optional[str] = Field(None, alias="jid") + id: str = Field(alias="jid") """The job ID.""" - system_id: Optional[str] = Field(None, alias="id") + system_id: str = Field(alias="id") """The ID of the system that the job targets.""" - created_timestamp: Optional[datetime] = None + created_timestamp: datetime """The timestamp representing when the job was created.""" - last_updated_timestamp: Optional[datetime] = None + last_updated_timestamp: datetime """The timestamp representing when the job was last updated.""" dispatched_timestamp: Optional[datetime] = None """The timestamp representing when the job was dispatched.""" - state: Optional[JobState] = None + state: JobState """The state of the job.""" - metadata: Optional[Dict[str, Any]] = None + metadata: Dict[str, Any] """The metadata associated with the job.""" - config: Optional[JobConfig] = None + config: JobConfig """The configuration of the job.""" result: Optional[JobResult] = None diff --git a/nisystemlink/clients/system/models/_job_summary_response.py b/nisystemlink/clients/system/models/_job_summary_response.py index 848a2b70..8c870776 100644 --- a/nisystemlink/clients/system/models/_job_summary_response.py +++ b/nisystemlink/clients/system/models/_job_summary_response.py @@ -7,7 +7,7 @@ class JobSummaryResponse(JsonModel): """Model for request of jobs summary response.""" - error: ApiError + error: Optional[ApiError] = None """Represents the standard error structure.""" active_count: int diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index 386a13c9..66694e13 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -6,7 +6,7 @@ class QueryJobsRequest(JsonModel): """Model for query job request.""" - skip: int + skip: Optional[int] = None """The number of jobs to skip.""" take: Optional[int] = None @@ -58,6 +58,7 @@ class QueryJobsRequest(JsonModel): Example: result.success.Contains(false) """ + #TODO: Refer Test monitor query results for converting this into list of strings. projection: Optional[str] = None """ Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. @@ -66,6 +67,7 @@ class QueryJobsRequest(JsonModel): 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' """ + #TODO: Refer Test monitor query results for converting this into orderBy Enum & descending property. order_by: Optional[str] = None """ The order in which the jobs return. diff --git a/nisystemlink/clients/system/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py index 4ed31a35..7d5a150b 100644 --- a/nisystemlink/clients/system/models/_query_jobs_response.py +++ b/nisystemlink/clients/system/models/_query_jobs_response.py @@ -9,7 +9,7 @@ class QueryJobsResponse(JsonModel): """Model for response of a query request.""" - error: ApiError + error: Optional[ApiError] = None """Represents the standard error structure.""" data: Optional[List[Job]] = None diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index 1d615115..931bdb9d 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -266,12 +266,18 @@ def test__cancel_multiple_jobs__cancel_multiple_job_succeeds( assert cancel_response is None - def test__cancel_with_invalid_jid__cancel_job_returns_error( + def test__cancel_with_invalid_jid_system_id__cancel_job_returns_None( self, client: SystemClient ): - cancel_job_request = CancelJobRequest(id="Invalid_jid", tgt="Invalid_tgt") + cancel_job_request = CancelJobRequest(id="Invalid_jid", system_id="Invalid_tgt") cancel_response = client.cancel_jobs([cancel_job_request]) - assert cancel_response is not None - assert cancel_response.error is not None - assert cancel_response.error.message is not None + assert cancel_response is None + + def test__cancel_with_invalid_jid_valid_system_id__cancel_job_returns_error( + self, client: SystemClient + ): + cancel_job_request = CancelJobRequest(id="Invalid_jid", system_id=TARGET_SYSTEM) + cancel_response = client.cancel_jobs([cancel_job_request]) + + assert cancel_response is None From 0e33a8728c19a022a0f57b1d52c9246b839aab1a Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Tue, 10 Dec 2024 17:11:44 +0530 Subject: [PATCH 19/23] refactor: add wrapping for job client. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/_system_client.py | 31 ++++- .../clients/system/models/__init__.py | 4 +- nisystemlink/clients/system/models/_job.py | 16 +-- .../system/models/_query_jobs_request.py | 108 +++++++++++++++++- .../system/models/_query_jobs_response.py | 13 ++- 5 files changed, 157 insertions(+), 15 deletions(-) diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index 46179ee7..ea4f23aa 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -107,7 +107,7 @@ def get_job_summary(self) -> models.JobSummaryResponse: ... @post("query-jobs") - def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse: + def _query_jobs(self, query: models._QueryJobsRequest) -> models.QueryJobsResponse: """Query the jobs. Args: @@ -122,6 +122,35 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse """ ... + def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse: + """Query the jobs. + + Args: + query: The request to query the jobs. + + Returns: + An instance of QueryJobsRequest. + + Raises: + ApiException: if unable to communicate with the ``/nisysmgmt`` Service + or provided an invalid argument. + """ + projection = ",".join(query.projection) + projection = f"new({projection})" if projection else "" + + order_by = ( + f"{query.order_by} {'descending' if query.descending else 'ascending'}" + ) + query_request = models._QueryJobsRequest( + skip=query.skip, + take=query.take, + filter=query.filter, + projection=projection, + order_by=order_by, + ) + + return self._query_jobs(query_request) + @response_handler(_cancel_job_response_handler) @post("cancel-jobs") def cancel_jobs( diff --git a/nisystemlink/clients/system/models/__init__.py b/nisystemlink/clients/system/models/__init__.py index f6c1e59e..92065796 100644 --- a/nisystemlink/clients/system/models/__init__.py +++ b/nisystemlink/clients/system/models/__init__.py @@ -2,8 +2,8 @@ from ._create_job_request import CreateJobRequest from ._create_job_response import CreateJobResponse from ._job_summary_response import JobSummaryResponse -from ._query_jobs_request import QueryJobsRequest -from ._query_jobs_response import QueryJobsResponse +from ._query_jobs_request import QueryJobsRequest, _QueryJobsRequest +from ._query_jobs_response import QueryJobsResponse, ScheduledJob from ._cancel_job_request import CancelJobRequest # flake8: noqa diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index 238982c4..23db0bc2 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -7,7 +7,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class JobState(Enum): +class JobState(str, Enum): """The state of the job.""" SUCCEEDED = "SUCCEEDED" @@ -54,28 +54,28 @@ class JobResult(JsonModel): class Job(JsonModel): """Job Model.""" - id: str = Field(alias="jid") + id: Optional[str] = Field(None, alias="jid") """The job ID.""" - system_id: str = Field(alias="id") + system_id: Optional[str] = Field(None, alias="id") """The ID of the system that the job targets.""" - created_timestamp: datetime + created_timestamp: Optional[datetime] = None """The timestamp representing when the job was created.""" - last_updated_timestamp: datetime + last_updated_timestamp: Optional[datetime] = None """The timestamp representing when the job was last updated.""" dispatched_timestamp: Optional[datetime] = None """The timestamp representing when the job was dispatched.""" - state: JobState + state: Optional[JobState] = None """The state of the job.""" - metadata: Dict[str, Any] + metadata: Optional[Dict[str, Any]] = None """The metadata associated with the job.""" - config: JobConfig + config: Optional[JobConfig] = None """The configuration of the job.""" result: Optional[JobResult] = None diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index 66694e13..304873b5 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -1,8 +1,34 @@ -from typing import Optional +from enum import Enum +from typing import Optional, List from nisystemlink.clients.core._uplink._json_model import JsonModel +class JobField(str, Enum): + """The fields of the job.""" + + ID = "jid" + SYSTEM_ID = "id" + CREATED_TIMESTAMP = "createdTimestamp" + LAST_UPDATED_TIMESTAMP = "lastUpdatedTimestamp" + DISPATCHED_TIMESTAMP = "dispatchedTimestamp" + SCHEDULED_TIMESTAMP = "scheduledTimestamp" + COMPLETED_TIMESTAMP = "completedTimestamp" + STATE = "state" + METADATA = "metadata" + CONFIG = "config" + CONFIG_USER = "config.user" + CONFIG_TARGET_SYSTEMS = "config.tgt" + CONFIG_FUNCTIONS = "config.fun" + CONFIG_ARGUMENTS = "config.arg" + RESULT = "result" + RESULT_JOB_ID = "result.jid" + RESULT_SYSTEM_ID = "result.id" + RESULT_RETURN = "result.return" + RESULT_RETURN_CODE = "result.retcode" + RESULT_SUCCESS = "result.success" + + class QueryJobsRequest(JsonModel): """Model for query job request.""" @@ -58,7 +84,84 @@ class QueryJobsRequest(JsonModel): Example: result.success.Contains(false) """ - #TODO: Refer Test monitor query results for converting this into list of strings. + # TODO: Refer Test monitor query results for converting this into list of strings. + projection: List[JobField | str] = [] + """ + Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. + + Examples: - 'new(id,jid,state)' - 'new(id,jid,config.user as user)' - + 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' + """ + + # TODO: Refer Test monitor query results for converting this into orderBy Enum & descending property. + order_by: Optional[JobField] = None + """ + The order in which the jobs return. + + Example: createdTimestamp descending + """ + + descending: bool = False + """ + The order in which the jobs return. Default is ascending. + """ + + +class _QueryJobsRequest(JsonModel): + """Model for query job request.""" + + skip: Optional[int] = None + """The number of jobs to skip.""" + + take: Optional[int] = None + """The number of jobs to return. The maximum value is 1000.""" + + filter: Optional[str] = None + """ + Gets or sets the filter criteria for jobs or systems. Consists of a string of queries composed using + AND/OR operators.String values and date strings need to be enclosed in double quotes. Parenthesis + can be used around filters to better define the order of operations. + Filter syntax: '[property name][operator][operand] and [property name][operator][operand]' + + Operators: + Equals operator '='. Example: 'x = y' + Not equal operator '!='. Example: 'x != y' + Greater than operator '>'. Example: 'x > y' + Greater than or equal operator '>='. Example: 'x >= y' + Less than operator '<'. Example: 'x < y' + Less than or equal operator '<='. Example: 'x <= y' + Logical AND operator 'and' or '&&'. Example: 'x and y' + Logical OR operator 'or' or '||'. Example: 'x or y' + Contains operator '.Contains()', used to check if a list contains an element. + Example: 'x.Contains(y)' + Not Contains operator '!.Contains()', used to check if a list does not contain an element. + Example: '!x.Contains(y)' + + Valid job properties that can be used in the filter: + jid : String representing the ID of the job. + id : String representing the ID of the system. + createdTimestamp: ISO-8601 formatted timestamp string specifying the date when the job + was created. + lastUpdatedTimestamp: ISO-8601 formatted timestamp string specifying the last date the + job was updated. + dispatchedTimestamp: ISO-8601 formatted timestamp string specifying the date when the + job was actually sent to the system. + state: String representing the state of the job. + metadata: Object containg the the metadata of job. Example: metadata.queued + config.user: String representing the user who created the job. + config.tgt: List of strings representing the targeted systems. Example: config.tgt.Contains("id") + config.fun: List of strings representing the functions to be executed within + the job. Example: config.fun.Contains("nisysmgmt.set_blackout") + config.arg: An array of arrays of variable type elements that are arguments to the function specified + by the "fun" property. Example: config.arg[0].Contains("test") + result.return: An array of objects representing return values for each executed function. + Example: result.return[0].Contains("Success") + result.retcode: An array of integers representing code values for each executed function. + Example: result.retcode + result.success: An array of booleans representing success values for each executed function. + Example: result.success.Contains(false) + """ + projection: Optional[str] = None """ Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. @@ -67,7 +170,6 @@ class QueryJobsRequest(JsonModel): 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' """ - #TODO: Refer Test monitor query results for converting this into orderBy Enum & descending property. order_by: Optional[str] = None """ The order in which the jobs return. diff --git a/nisystemlink/clients/system/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py index 7d5a150b..4575f37e 100644 --- a/nisystemlink/clients/system/models/_query_jobs_response.py +++ b/nisystemlink/clients/system/models/_query_jobs_response.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import List, Optional from nisystemlink.clients.core import ApiError @@ -6,13 +7,23 @@ from ._job import Job +class ScheduledJob(Job): + """Represents a scheduled job.""" + + scheduled_timestamp: Optional[datetime] = None + """The timestamp when the job was scheduled.""" + + completed_timestamp: Optional[datetime] = None + """The timestamp when the job was completed.""" + + class QueryJobsResponse(JsonModel): """Model for response of a query request.""" error: Optional[ApiError] = None """Represents the standard error structure.""" - data: Optional[List[Job]] = None + data: Optional[List[ScheduledJob]] = None """The data returned by the query.""" count: int From 5575f20bf05e7931c9eb11f26c428f2f053623ce Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 11 Dec 2024 20:38:05 +0530 Subject: [PATCH 20/23] refactor: modify clients & tests. Signed-off-by: Ganesh Nithin --- examples/system/job.py | 2 +- nisystemlink/clients/core/_uplink/_methods.py | 1 + nisystemlink/clients/system/_system_client.py | 36 +- .../clients/system/models/__init__.py | 4 +- .../system/models/_create_job_request.py | 2 +- nisystemlink/clients/system/models/_job.py | 23 +- .../system/models/_query_jobs_request.py | 21 +- .../system/models/_query_jobs_response.py | 58 ++- .../integration/system/test_system_client.py | 460 ++++++++++++------ 9 files changed, 434 insertions(+), 173 deletions(-) diff --git a/examples/system/job.py b/examples/system/job.py index 5fe0283e..481ae698 100644 --- a/examples/system/job.py +++ b/examples/system/job.py @@ -17,7 +17,7 @@ # Get all jobs that have succeeded jobs = client.list_jobs( system_id="system_id", - jid="jid", + job_id="jid", state=JobState.SUCCEEDED, function="function", skip=0, diff --git a/nisystemlink/clients/core/_uplink/_methods.py b/nisystemlink/clients/core/_uplink/_methods.py index bed04703..646f62ab 100644 --- a/nisystemlink/clients/core/_uplink/_methods.py +++ b/nisystemlink/clients/core/_uplink/_methods.py @@ -17,6 +17,7 @@ def get(path: str, args: Optional[Sequence[Any]] = None) -> Callable[[F], F]: """Annotation for a GET request.""" def decorator(func: F) -> F: + print("get decorator", func) return commands.get(path, args=args)(func) # type: ignore return decorator diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index ea4f23aa..07a6d280 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -1,4 +1,5 @@ -from typing import List, Optional, Union +from typing import List, Optional, Union, Any +import json from nisystemlink.clients import core from nisystemlink.clients.core import ApiError @@ -15,11 +16,24 @@ def _cancel_job_response_handler(response: Response) -> Union[ApiError, None]: if response is None: return None - cancel_response = response.json() + try: + cancel_response = response.json() + except json.JSONDecodeError: + return None return cancel_response.get("error") +def _list_jobs_response_handler(response: Response) -> List[models.Job]: + """Response handler for List Jobs response.""" + if response is None: + return [] + + jobs = response.json() + + return jobs + + class SystemClient(BaseClient): def __init__(self, configuration: Optional[core.HttpConfiguration] = None): """Initialize an instance. @@ -38,6 +52,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): super().__init__(configuration, "/nisysmgmt/v1/") + @response_handler(_list_jobs_response_handler) @get( "jobs", args=[ @@ -52,7 +67,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): def list_jobs( self, system_id: Optional[str] = None, - jid: Optional[str] = None, + job_id: Optional[str] = None, state: Optional[models.JobState] = None, function: Optional[str] = None, skip: Optional[int] = None, @@ -135,19 +150,30 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ + projection = ",".join(query.projection) projection = f"new({projection})" if projection else "" order_by = ( - f"{query.order_by} {'descending' if query.descending else 'ascending'}" + f"{query.order_by.strip()} {'descending' if query.descending else 'ascending'}" + if query.order_by + else None ) + query_request = models._QueryJobsRequest( skip=query.skip, take=query.take, filter=query.filter, projection=projection, order_by=order_by, - ) + ).dict() + + # Remove None values from the dictionary + query_params = {k: v for k, v in query_request.items() if v is not None} + + query_request = models._QueryJobsRequest(**query_params) + + print(query_request) return self._query_jobs(query_request) diff --git a/nisystemlink/clients/system/models/__init__.py b/nisystemlink/clients/system/models/__init__.py index 92065796..927eacaf 100644 --- a/nisystemlink/clients/system/models/__init__.py +++ b/nisystemlink/clients/system/models/__init__.py @@ -2,8 +2,8 @@ from ._create_job_request import CreateJobRequest from ._create_job_response import CreateJobResponse from ._job_summary_response import JobSummaryResponse -from ._query_jobs_request import QueryJobsRequest, _QueryJobsRequest -from ._query_jobs_response import QueryJobsResponse, ScheduledJob +from ._query_jobs_request import QueryJobsRequest, _QueryJobsRequest, JobField +from ._query_jobs_response import QueryJobsResponse, QueryJob, QueryJobConfig from ._cancel_job_request import CancelJobRequest # flake8: noqa diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index 47be26d7..343a859d 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -7,7 +7,7 @@ class CreateJobRequest(JsonModel): """Model for create job request.""" - arguments: Optional[List[List[Any]]] = Field(None, alias="args") + arguments: Optional[List[List[Any]]] = Field(None, alias="arg") """List of arguments to the functions specified in the "fun" property.""" target_systems: List[str] = Field(alias="tgt") diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index 23db0bc2..c2ad570b 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -30,7 +30,7 @@ class JobConfig(JsonModel): functions: List[str] = Field(alias="fun") """Salt functions related to the job.""" - arguments: Optional[List[List[Any]]] = Field(None, alias="args") + arguments: Optional[List[List[Any]]] = Field(None, alias="arg") """Arguments of the salt functions.""" @@ -41,45 +41,42 @@ class JobResult(JsonModel): system_id: Optional[str] = Field(None, alias="id") """The ID of the system that the job targets.""" - return_code: Optional[List[int]] = None + return_code: Optional[List[int]] = Field(None, alias="retcode") """Return code of the job.""" return_: Optional[List[Any]] = None """Return value of the job.""" - success: Optional[bool] = None + success: Optional[List[bool]] = None """Whether the job was successful.""" class Job(JsonModel): """Job Model.""" - id: Optional[str] = Field(None, alias="jid") + id: str = Field(alias="jid") """The job ID.""" - system_id: Optional[str] = Field(None, alias="id") + system_id: str = Field(alias="id") """The ID of the system that the job targets.""" - created_timestamp: Optional[datetime] = None + created_timestamp: datetime """The timestamp representing when the job was created.""" - last_updated_timestamp: Optional[datetime] = None + last_updated_timestamp: datetime """The timestamp representing when the job was last updated.""" dispatched_timestamp: Optional[datetime] = None """The timestamp representing when the job was dispatched.""" - state: Optional[JobState] = None + state: JobState """The state of the job.""" - metadata: Optional[Dict[str, Any]] = None + metadata: Dict[str, Any] """The metadata associated with the job.""" - config: Optional[JobConfig] = None + config: JobConfig """The configuration of the job.""" result: Optional[JobResult] = None """The result of the job.""" - - class Config: - orm_mode = True diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index 304873b5..4bc16269 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -8,24 +8,43 @@ class JobField(str, Enum): """The fields of the job.""" ID = "jid" + SYSTEM_ID = "id" + CREATED_TIMESTAMP = "createdTimestamp" + LAST_UPDATED_TIMESTAMP = "lastUpdatedTimestamp" + DISPATCHED_TIMESTAMP = "dispatchedTimestamp" + SCHEDULED_TIMESTAMP = "scheduledTimestamp" - COMPLETED_TIMESTAMP = "completedTimestamp" + + COMPLETING_TIMESTAMP = "completingTimestamp" + STATE = "state" + METADATA = "metadata" + CONFIG = "config" + CONFIG_USER = "config.user" + CONFIG_TARGET_SYSTEMS = "config.tgt" + CONFIG_FUNCTIONS = "config.fun" + CONFIG_ARGUMENTS = "config.arg" + RESULT = "result" + RESULT_JOB_ID = "result.jid" + RESULT_SYSTEM_ID = "result.id" + RESULT_RETURN = "result.return" + RESULT_RETURN_CODE = "result.retcode" + RESULT_SUCCESS = "result.success" diff --git a/nisystemlink/clients/system/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py index 4575f37e..ce5f3f0c 100644 --- a/nisystemlink/clients/system/models/_query_jobs_response.py +++ b/nisystemlink/clients/system/models/_query_jobs_response.py @@ -1,21 +1,65 @@ from datetime import datetime -from typing import List, Optional +from pydantic import Field +from typing import List, Optional, Dict, Any from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from ._job import Job +from ._job import JobState, JobResult -class ScheduledJob(Job): - """Represents a scheduled job.""" +class QueryJobConfig(JsonModel): + """The configuration of the job.""" + + user: Optional[str] = None + """The user who created the job.""" + + target_systems: Optional[List[str]] = Field(None, alias="tgt") + """The target systems for the job.""" + + functions: Optional[List[str]] = Field(None, alias="fun") + """Salt functions related to the job.""" + + arguments: Optional[List[List[Any]]] = Field(None, alias="arg") + """Arguments of the salt functions.""" + + +class QueryJob(JsonModel): + """Job Modal for query response.""" + + id: Optional[str] = Field(None, alias="jid") + """The job ID.""" + + system_id: Optional[str] = Field(None, alias="id") + """The ID of the system that the job targets.""" + + created_timestamp: Optional[datetime] = None + """The timestamp representing when the job was created.""" + + last_updated_timestamp: Optional[datetime] = None + """The timestamp representing when the job was last updated.""" + + dispatched_timestamp: Optional[datetime] = None + """The timestamp representing when the job was dispatched.""" scheduled_timestamp: Optional[datetime] = None """The timestamp when the job was scheduled.""" - completed_timestamp: Optional[datetime] = None + completing_timestamp: Optional[datetime] = None """The timestamp when the job was completed.""" + state: Optional[JobState] = None + """The state of the job.""" + + metadata: Optional[Dict[str, Any]] = None + """The metadata associated with the job.""" + + config: Optional[QueryJobConfig] = None + """The configuration of the job.""" + + result: Optional[JobResult] = None + """The result of the job.""" + class QueryJobsResponse(JsonModel): """Model for response of a query request.""" @@ -23,8 +67,8 @@ class QueryJobsResponse(JsonModel): error: Optional[ApiError] = None """Represents the standard error structure.""" - data: Optional[List[ScheduledJob]] = None + data: Optional[List[QueryJob]] = None """The data returned by the query.""" - count: int + count: Optional[int] = None """The total number of resources that matched the query.""" diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index 931bdb9d..b440bef9 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -1,4 +1,9 @@ from typing import List +from unittest.mock import MagicMock, patch +from uuid import uuid4 +import responses.matchers +from uplink import commands +import responses import pytest from nisystemlink.clients.core import ApiException @@ -9,12 +14,11 @@ CreateJobRequest, CreateJobResponse, QueryJobsRequest, + _QueryJobsRequest, + JobField, ) -TARGET_SYSTEM = ( - # "HVM_domU--SN-ec200972-eeca-062e-5bf5-33g3g3g3d73b2--MAC-0A-E1-20-D6-96-2B" - "20UAS1L61D--SN-PF2K5S1M--MAC-8C-8C-AA-82-6A-F8" -) +TARGET_SYSTEM = "dh33jg-43erhqfb-3r3r3r" METADATA = {"queued": True, "refresh_minion_cache": {"grains": True}} SAMPLE_FUN_1 = "system.sample_function_one" SAMPLE_FUN_2 = "system.sample_function_two" @@ -26,77 +30,13 @@ def client(enterprise_config: HttpConfiguration) -> SystemClient: return SystemClient(enterprise_config) -@pytest.fixture(scope="class") -def create_job( - client: SystemClient, -): - """Fixture to create a job.""" - responses: List[CreateJobResponse] = [] - - def _create_job(job: CreateJobRequest) -> CreateJobResponse: - response = client.create_job(job) - responses.append(response) - return response - - yield _create_job - - job_requests = [ - CancelJobRequest(id=response.id, system_id=TARGET_SYSTEM) - for response in responses - if response.id != "" - ] - - if len(job_requests): - client.cancel_jobs(job_requests) - - -@pytest.fixture(scope="class", autouse=True) -def create_multiple_jobs( - create_job, -): - """Fixture to create multiple jobs.""" - responses = [] - arg_1 = [["sample argument one"]] - arg_2 = [["sample argument two"]] - tgt = [TARGET_SYSTEM] - fun_1 = [SAMPLE_FUN_1] - fun_2 = [SAMPLE_FUN_2] - - metadata = METADATA - - job_1 = CreateJobRequest( - arguments=arg_1, - target_systems=tgt, - functions=fun_1, - metadata=metadata, - ) - responses.append(create_job(job_1)) - - job_2 = CreateJobRequest( - arguments=arg_2, - target_systems=tgt, - functions=fun_2, - metadata=metadata, - ) - responses.append(create_job(job_2)) - - job_3 = CreateJobRequest( - arguments=arg_1, - target_systems=tgt, - functions=fun_2, - metadata=metadata, - ) - responses.append(create_job(job_3)) - - return responses - - @pytest.mark.integration @pytest.mark.enterprise class TestSystemClient: + @responses.activate def test__create_a_job__job_is_created_with_right_field_values( self, - create_job, + client: SystemClient, ): arg = [["sample argument"]] tgt = [TARGET_SYSTEM] @@ -108,48 +48,169 @@ def test__create_a_job__job_is_created_with_right_field_values( functions=fun, metadata=METADATA, ) + job_id = str(uuid4()) + + return_value = { + "jid": job_id, + "tgt": job.target_systems, + "fun": job.functions, + "arg": job.arguments, + "metadata": job.metadata, + "error": None, + } + + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/jobs", + json=return_value, + status=201, + ) - response = create_job(job) + response = client.create_job(job) assert response is not None - assert response.id != "" + assert response.id == job_id assert response.target_systems == tgt assert response.error is None - def test__get_job_using_target_and_job_id__returns_job_matches_target_and_job_id( - self, create_multiple_jobs, client: SystemClient + @responses.activate + def test__create_job_with_invalid_target_system__return_error_response( + self, + client: SystemClient, ): - [first_job, *_] = create_multiple_jobs - print(first_job.jid) - response = client.list_jobs(system_id=TARGET_SYSTEM, jid=first_job.jid) - assert len(response) == 1 - [response_job] = response - print(response) - assert response_job.id == first_job.jid - assert response_job.config is not None - assert response_job.config.target_systems == first_job.tgt + arg = [["sample argument"]] + tgt = ["Invalid_target_system"] + fun = ["system.set_computer_desc_sample_function"] - def test__get_jobs_using_target_and_function__return_jobs_match_target_and_function( - self, create_multiple_jobs, client: SystemClient - ): - [_, second_job, third_job] = create_multiple_jobs - response = client.list_jobs(system_id=TARGET_SYSTEM, function=SAMPLE_FUN_2) - assert len(response) == 2 + job = CreateJobRequest( + arguments=arg, + target_systems=tgt, + functions=fun, + metadata=METADATA, + ) + + return_value = { + "jid": "", + "tgt": job.target_systems, + "fun": job.functions, + "arg": job.arguments, + "metadata": job.metadata, + "error": { + "name": "Skyline.OneOrMoreErrorsOccurred", + "code": -251041, + "message": "One or more errors occurred. See the contained list for details of each error.", + "args": [], + "innerErrors": [ + { + "name": "SystemsManagement.SystemNotFound", + "code": -254010, + "message": "System not found.", + "resourceType": "Minion", + "resourceId": "Invalid_target_system", + "args": ["Invalid_target_system"], + "innerErrors": [], + } + ], + }, + } + + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/jobs", + json=return_value, + status=201, + ) + + response = client.create_job(job) - [response_second_job, response_third_job] = response + assert response is not None + assert response.id == "" + assert response.target_systems == tgt + assert response.error is not None - assert response_second_job.id == second_job.jid - assert response_second_job.config is not None - assert response_second_job.config.target_systems == second_job.tgt + @responses.activate + def test__get_job_using_target_and_job_id__returns_job_matches_target_and_job_id( + self, + client: SystemClient, + ): + job_id = "sample_job_id" + return_value = [ + { + "jid": job_id, + "id": TARGET_SYSTEM, + "createdTimestamp": "2024-11-12T06:00:02.212+00:00", + "lastUpdatedTimestamp": "2024-11-12T11:05:38.614+00:00", + "state": "CANCELED", + "config": { + "user": "admin", + "tgt": [TARGET_SYSTEM], + "fun": [SAMPLE_FUN_1], + }, + "metadata": METADATA, + }, + ] + + responses.add( + method=responses.GET, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/jobs", + json=return_value, + status=200, + ) - assert response_third_job.id == third_job.jid - assert response_third_job.config is not None - assert response_third_job.config.target_systems == third_job.tgt + list_response = client.list_jobs(job_id=job_id, system_id=TARGET_SYSTEM) + assert len(list_response) == 1 + assert list_response[0].id == job_id + assert list_response[0].system_id == TARGET_SYSTEM - def test__get_job_by_taking_one__return_only_one_job( - self, create_multiple_jobs, client: SystemClient + @responses.activate + def test__get_jobs_using_target_and_function__return_jobs_match_target_and_function( + self, client: SystemClient ): - response = client.list_jobs(system_id=TARGET_SYSTEM, skip=1) + job_id_1 = "sample_job_id_1" + job_id_2 = "sample_job_id_2" + return_value = [ + { + "jid": job_id_1, + "id": TARGET_SYSTEM, + "createdTimestamp": "2024-11-12T06:00:02.212+00:00", + "lastUpdatedTimestamp": "2024-11-12T11:05:38.614+00:00", + "state": "CANCELED", + "config": { + "user": "admin", + "tgt": [TARGET_SYSTEM], + "fun": [SAMPLE_FUN_1], + }, + "metadata": METADATA, + }, + { + "jid": job_id_2, + "id": TARGET_SYSTEM, + "createdTimestamp": "2024-11-12T06:00:02.212+00:00", + "lastUpdatedTimestamp": "2024-11-12T11:05:38.614+00:00", + "state": "CANCELED", + "config": { + "user": "admin", + "tgt": [TARGET_SYSTEM], + "fun": [SAMPLE_FUN_1], + }, + "metadata": METADATA, + }, + ] + + responses.add( + method=responses.GET, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/jobs", + json=return_value, + status=200, + ) + + list_response = client.list_jobs(system_id=TARGET_SYSTEM, function=SAMPLE_FUN_1) + assert len(list_response) == 2 + assert list_response[0].id == job_id_1 + assert list_response[1].id == job_id_2 + + def test__get_job_by_taking_one__return_only_one_job(self, client: SystemClient): + response = client.list_jobs(take=1) assert len(response) == 1 def test__get_jobs_using_invalid_system_id__returns_empty_list( @@ -161,7 +222,7 @@ def test__get_jobs_using_invalid_system_id__returns_empty_list( def test__get_jobs_using_invalid_jid__returns_empty_list( self, client: SystemClient ): - response = client.list_jobs(jid="Invalid_jid") + response = client.list_jobs(job_id="Invalid_jid") assert len(response) == 0 def test__get_job_summary__returns_job_summary(self, client: SystemClient): @@ -174,92 +235,205 @@ def test__get_job_summary__returns_job_summary(self, client: SystemClient): assert response.error is None def test__query_jobs_by_taking_one__returns_one_job(self, client: SystemClient): - query = QueryJobsRequest(skip=0, take=1) + query = QueryJobsRequest(take=1) response = client.query_jobs(query=query) assert response is not None assert response.data is not None assert len(response.data) == response.count == 1 + @responses.activate def test__query_jobs_by_filtering_config__return_jobs_matches_filter( self, client: SystemClient ): - query = QueryJobsRequest(skip=0, filter=f"config.fun.Contains({SAMPLE_FUN_2})") + job_id = "sample_job_id" + return_value = { + "data": [ + { + "jid": job_id, + "id": TARGET_SYSTEM, + "createdTimestamp": "2024-11-12T06:00:02.212+00:00", + "lastUpdatedTimestamp": "2024-11-12T11:05:38.614+00:00", + "state": "CANCELED", + "config": { + "user": "admin", + "tgt": [TARGET_SYSTEM], + "fun": [SAMPLE_FUN_1], + }, + "metadata": METADATA, + }, + ], + "count": 1, + } + + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/query-jobs", + json=return_value, + status=200, + ) + + query = QueryJobsRequest(filter=f"config.fun.Contains({SAMPLE_FUN_1})") response = client.query_jobs(query=query) assert response is not None assert response.data is not None assert response.count is not None - assert len(response.data) == response.count > 0 + assert len(response.data) == response.count == 1 + assert response.data[0].id == job_id + assert response.data[0].config.functions == [SAMPLE_FUN_1] - def test__query_jobs_by_filtering_invalid_filter__raises_ApiException( + def test__query_jobs_by_filtering_invalid_function__returns_empty_list( self, client: SystemClient ): - query = QueryJobsRequest( - skip=0, filter='config.fun.Contains("failed_function")' - ) - with pytest.raises(ApiException): - client.query_jobs(query=query) + query = QueryJobsRequest(filter='config.fun.Contains("failed_function")') + response = client.query_jobs(query=query) + + assert response.error is None + assert len(response.data) == 0 + assert response.count == 0 + @responses.activate def test__query_jobs_by_filtering_jid__returns_job_matches_jid( - self, create_multiple_jobs, client: SystemClient + self, client: SystemClient ): - query = QueryJobsRequest(skip=0, filter=f"jid={create_multiple_jobs[0].jid}") + job_id = "sample_job_id" + return_value = { + "data": [ + { + "jid": job_id, + "id": TARGET_SYSTEM, + "createdTimestamp": "2024-11-12T06:00:02.212+00:00", + "lastUpdatedTimestamp": "2024-11-12T11:05:38.614+00:00", + "state": "CANCELED", + "config": { + "user": "admin", + "tgt": [TARGET_SYSTEM], + "fun": [SAMPLE_FUN_1], + }, + "metadata": METADATA, + }, + ], + "count": 1, + } + + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/query-jobs", + json=return_value, + status=200, + ) + query = QueryJobsRequest(filter=f"jid={job_id}") response = client.query_jobs(query=query) assert response is not None assert response.data is not None assert len(response.data) == response.count == 1 + assert response.data[0].id == job_id - def test__query_jobs_by_filtering_invalid_jid__raises_ApiException( + def test__query_jobs_by_filtering_invalid_jid__raises_ApiException_BadRequest( self, client: SystemClient ): - query = QueryJobsRequest(skip=0, filter="jid=Invalid_jid") - with pytest.raises(ApiException): + query = QueryJobsRequest(filter="jid=Invalid_jid") + with pytest.raises(ApiException, match="Bad Request"): client.query_jobs(query=query) + def test__query_jobs_by_filtering_invalid_system_id__raises_ApiException( + self, client: SystemClient + ): + query = QueryJobsRequest(filter="id=Invalid_system_id") + with pytest.raises(ApiException, match="Bad Request"): + client.query_jobs(query=query) + + def test__query_jobs_by_projecting_job_id_and_system_id__returns_jobs_with_only_job_id_and_system_id_properties( + self, + client: SystemClient, + ): + query = QueryJobsRequest(projection=[JobField.ID, JobField.SYSTEM_ID], take=3) + response = client.query_jobs(query=query) + + assert response is not None + assert response.data is not None + assert len(response.data) == response.count == 3 + + assert all( + job.id is not None + and job.system_id is not None + and job.created_timestamp is None + and job.last_updated_timestamp is None + and job.state is None + and job.config is None + and job.metadata is None + and job.result is None + for job in response.data + ) + + def test__query_jobs_with_invalid_projection__raises_ApiException_BadRequest( + self, client: SystemClient + ): + query = QueryJobsRequest(projection=["Invalid_projection"], take=3) + with pytest.raises(ApiException, match="Bad Request"): + client.query_jobs(query=query) + + def test__query_jobs_order_by_created_timestamp_in_ascending_order__returns_jobs_sorted_by_created_timestamp_in_ascending_order( + self, client: SystemClient + ): + query = QueryJobsRequest(order_by=JobField.CREATED_TIMESTAMP, take=3) + response = client.query_jobs(query=query) + + assert response is not None + assert response.data is not None + assert len(response.data) == response.count == 3 + + assert all( + response.data[i].created_timestamp <= response.data[i + 1].created_timestamp + for i in range(len(response.data) - 1) + ) + + def test__query_jobs_order_by_completing_timestamp_in_descending_order__returns_jobs_sorted_by_completing_timestamp_in_descending_order( + self, client: SystemClient + ): + query = QueryJobsRequest( + order_by=JobField.COMPLETING_TIMESTAMP, descending=True, take=3 + ) + response = client.query_jobs(query=query) + + assert response is not None + assert response.data is not None + assert len(response.data) == response.count == 3 + + assert all( + response.data[i].completing_timestamp + >= response.data[i + 1].completing_timestamp + for i in range(len(response.data) - 1) + ) + + @responses.activate def test__cancel_single_job__cancel_single_job_succeeds(self, client: SystemClient): - arg = [["sample argument"]] - tgt = [TARGET_SYSTEM] - fun = [SAMPLE_FUN_1] - job = CreateJobRequest( - arguments=arg, - target_systems=tgt, - functions=fun, - metadata=METADATA, + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/cancel-jobs", + status=200, ) - response = client.create_job(job) - cancel_job_request = CancelJobRequest(id=response.id, tgt=TARGET_SYSTEM) + cancel_job_request = CancelJobRequest(id="Job.id", system_id=TARGET_SYSTEM) cancel_response = client.cancel_jobs([cancel_job_request]) assert cancel_response is None + @responses.activate def test__cancel_multiple_jobs__cancel_multiple_job_succeeds( self, client: SystemClient ): - arg_1 = [["sample argument one"]] - arg_2 = [["sample argument two"]] - tgt = [TARGET_SYSTEM] - fun = [SAMPLE_FUN_2] - job_1 = CreateJobRequest( - arguments=arg_1, - target_systems=tgt, - functions=fun, - metadata=METADATA, - ) - response_1 = client.create_job(job_1) - job_2 = CreateJobRequest( - arguments=arg_2, - target_systems=tgt, - functions=fun, - metadata=METADATA, + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/cancel-jobs", + status=200, ) - response_2 = client.create_job(job_2) - cancel_job_request_1 = CancelJobRequest(id=response_1.id, tgt=TARGET_SYSTEM) - cancel_job_request_2 = CancelJobRequest(id=response_2.id, tgt=TARGET_SYSTEM) + cancel_job_request_1 = CancelJobRequest(id="Job_1.id", system_id=TARGET_SYSTEM) + cancel_job_request_2 = CancelJobRequest(id="Job_2.id", system_id=TARGET_SYSTEM) cancel_response = client.cancel_jobs( [cancel_job_request_1, cancel_job_request_2] ) From ee1d908e3f01fd4ba9f33a390815dcc76d7dbb87 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 11 Dec 2024 22:49:49 +0530 Subject: [PATCH 21/23] refactor: format system client code. Signed-off-by: Ganesh Nithin --- examples/system/job.py | 24 +++-- nisystemlink/clients/system/_system_client.py | 28 +++--- .../system/models/_cancel_job_request.py | 4 +- .../system/models/_create_job_request.py | 2 +- .../system/models/_create_job_response.py | 2 +- nisystemlink/clients/system/models/_job.py | 8 +- .../system/models/_query_jobs_request.py | 9 +- .../system/models/_query_jobs_response.py | 6 +- .../integration/system/test_system_client.py | 89 +++++++++++++------ 9 files changed, 107 insertions(+), 65 deletions(-) diff --git a/examples/system/job.py b/examples/system/job.py index 481ae698..dd2464d8 100644 --- a/examples/system/job.py +++ b/examples/system/job.py @@ -3,6 +3,7 @@ from nisystemlink.clients.system.models import ( CancelJobRequest, CreateJobRequest, + JobField, JobState, QueryJobsRequest, ) @@ -25,14 +26,16 @@ ) # Create a job -arg = [["A description"]] -tgt = ["HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B"] -fun = ["system.set_computer_desc"] +arguments = [["A description"]] +target_systems = [ + "HVM_domU--SN-ec200972-eeca-062e-5bf5-017a25451b39--MAC-0A-E1-20-D6-96-2B" +] +functions = ["system.set_computer_desc"] metadata = {"queued": True, "refresh_minion_cache": {"grains": True}} job = CreateJobRequest( - arguments=arg, - target_systems=tgt, - functions=fun, + arguments=arguments, + target_systems=target_systems, + functions=functions, metadata=metadata, ) @@ -46,12 +49,15 @@ skip=0, take=1000, filter="result.success.Contains(false)", - projection="new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)", - orderBy="createdTimestamp descending", + projection=[JobField.ID, JobField.SYSTEM_ID, "metadata.queued"], + orderBy=JobField.CREATED_TIMESTAMP, + descending=True, ) query_jobs_response = client.query_jobs(query_job) # Cancel a job -cancel_job_request = CancelJobRequest(id=create_job_response.id, tgt=tgt[0]) +cancel_job_request = CancelJobRequest( + id=create_job_response.id, system_id=target_systems[0] +) cancel_job_response = client.cancel_jobs([cancel_job_request]) diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index 07a6d280..d1f154b5 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -1,12 +1,12 @@ -from typing import List, Optional, Union, Any import json +from typing import List, Optional, Union from nisystemlink.clients import core from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post, response_handler -from uplink import Query from requests.models import Response +from uplink import Query from . import models @@ -150,31 +150,29 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ - projection = ",".join(query.projection) projection = f"new({projection})" if projection else "" order_by = ( - f"{query.order_by.strip()} {'descending' if query.descending else 'ascending'}" + f"{query.order_by.value} {'descending' if query.descending else 'ascending'}" if query.order_by else None ) - query_request = models._QueryJobsRequest( - skip=query.skip, - take=query.take, - filter=query.filter, - projection=projection, - order_by=order_by, - ).dict() + query_params = { + "skip": query.skip if query.skip is not None else None, + "take": query.take if query.take is not None else None, + "filter": query.filter if query.filter is not None else None, + "projection": projection, # If None, this will be omitted + "order_by": order_by, # If None, this will be omitted + } - # Remove None values from the dictionary - query_params = {k: v for k, v in query_request.items() if v is not None} + # Clean the query_params to remove any keys with None values + query_params = {k: v for k, v in query_params.items() if v is not None} + # Create the query request with the cleaned parameters query_request = models._QueryJobsRequest(**query_params) - print(query_request) - return self._query_jobs(query_request) @response_handler(_cancel_job_response_handler) diff --git a/nisystemlink/clients/system/models/_cancel_job_request.py b/nisystemlink/clients/system/models/_cancel_job_request.py index 36c91e8f..e866c822 100644 --- a/nisystemlink/clients/system/models/_cancel_job_request.py +++ b/nisystemlink/clients/system/models/_cancel_job_request.py @@ -1,7 +1,5 @@ -from pydantic import Field -from typing import Optional - from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import Field class CancelJobRequest(JsonModel): diff --git a/nisystemlink/clients/system/models/_create_job_request.py b/nisystemlink/clients/system/models/_create_job_request.py index 343a859d..aa410bef 100644 --- a/nisystemlink/clients/system/models/_create_job_request.py +++ b/nisystemlink/clients/system/models/_create_job_request.py @@ -1,7 +1,7 @@ from typing import Any, Dict, List, Optional -from pydantic import Field from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import Field class CreateJobRequest(JsonModel): diff --git a/nisystemlink/clients/system/models/_create_job_response.py b/nisystemlink/clients/system/models/_create_job_response.py index 7f7db5b5..008f6a12 100644 --- a/nisystemlink/clients/system/models/_create_job_response.py +++ b/nisystemlink/clients/system/models/_create_job_response.py @@ -1,7 +1,7 @@ -from pydantic import Field from typing import Optional from nisystemlink.clients.core import ApiError +from pydantic import Field from ._create_job_request import CreateJobRequest diff --git a/nisystemlink/clients/system/models/_job.py b/nisystemlink/clients/system/models/_job.py index c2ad570b..c0e0c6ae 100644 --- a/nisystemlink/clients/system/models/_job.py +++ b/nisystemlink/clients/system/models/_job.py @@ -1,20 +1,24 @@ -from pydantic import Field from datetime import datetime from enum import Enum from typing import Any, Dict, List, Optional - from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import Field class JobState(str, Enum): """The state of the job.""" SUCCEEDED = "SUCCEEDED" + OUTOFQUEUE = "OUTOFQUEUE" + INQUEUE = "INQUEUE" + INPROGRESS = "INPROGRESS" + CANCELED = "CANCELED" + FAILED = "FAILED" diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index 4bc16269..9c370e88 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional, List +from typing import List, Optional from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -103,21 +103,18 @@ class QueryJobsRequest(JsonModel): Example: result.success.Contains(false) """ - # TODO: Refer Test monitor query results for converting this into list of strings. projection: List[JobField | str] = [] """ Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. - Examples: - 'new(id,jid,state)' - 'new(id,jid,config.user as user)' - - 'new(id,jid,state,lastUpdatedTimestamp,metadata.queued as queued)' + Examples: - [JobField.ID, JobField.SYSTEM_ID, metadata.queued] """ - # TODO: Refer Test monitor query results for converting this into orderBy Enum & descending property. order_by: Optional[JobField] = None """ The order in which the jobs return. - Example: createdTimestamp descending + Example: JobField.CREATED_TIMESTAMP """ descending: bool = False diff --git a/nisystemlink/clients/system/models/_query_jobs_response.py b/nisystemlink/clients/system/models/_query_jobs_response.py index ce5f3f0c..2b0c09ac 100644 --- a/nisystemlink/clients/system/models/_query_jobs_response.py +++ b/nisystemlink/clients/system/models/_query_jobs_response.py @@ -1,11 +1,11 @@ from datetime import datetime -from pydantic import Field -from typing import List, Optional, Dict, Any +from typing import Any, Dict, List, Optional from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import Field -from ._job import JobState, JobResult +from ._job import JobResult, JobState class QueryJobConfig(JsonModel): diff --git a/tests/integration/system/test_system_client.py b/tests/integration/system/test_system_client.py index b440bef9..011c7357 100644 --- a/tests/integration/system/test_system_client.py +++ b/tests/integration/system/test_system_client.py @@ -1,21 +1,14 @@ -from typing import List -from unittest.mock import MagicMock, patch -from uuid import uuid4 -import responses.matchers -from uplink import commands -import responses - import pytest -from nisystemlink.clients.core import ApiException +import responses +import responses.matchers +from nisystemlink.clients.core import ApiError, ApiException from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.system import SystemClient from nisystemlink.clients.system.models import ( CancelJobRequest, CreateJobRequest, - CreateJobResponse, - QueryJobsRequest, - _QueryJobsRequest, JobField, + QueryJobsRequest, ) TARGET_SYSTEM = "dh33jg-43erhqfb-3r3r3r" @@ -48,7 +41,7 @@ def test__create_a_job__job_is_created_with_right_field_values( functions=fun, metadata=METADATA, ) - job_id = str(uuid4()) + job_id = "sample_job_id" return_value = { "jid": job_id, @@ -281,6 +274,8 @@ def test__query_jobs_by_filtering_config__return_jobs_matches_filter( assert response.count is not None assert len(response.data) == response.count == 1 assert response.data[0].id == job_id + assert response.data[0].config is not None + assert response.data[0].config.functions is not None assert response.data[0].config.functions == [SAMPLE_FUN_1] def test__query_jobs_by_filtering_invalid_function__returns_empty_list( @@ -290,11 +285,12 @@ def test__query_jobs_by_filtering_invalid_function__returns_empty_list( response = client.query_jobs(query=query) assert response.error is None + assert response.data is not None assert len(response.data) == 0 assert response.count == 0 @responses.activate - def test__query_jobs_by_filtering_jid__returns_job_matches_jid( + def test__query_jobs_by_filtering_job_id__returns_job_matches_job_id( self, client: SystemClient ): job_id = "sample_job_id" @@ -331,7 +327,7 @@ def test__query_jobs_by_filtering_jid__returns_job_matches_jid( assert len(response.data) == response.count == 1 assert response.data[0].id == job_id - def test__query_jobs_by_filtering_invalid_jid__raises_ApiException_BadRequest( + def test__query_jobs_by_filtering_invalid_job_id__raises_ApiException_BadRequest( self, client: SystemClient ): query = QueryJobsRequest(filter="jid=Invalid_jid") @@ -375,7 +371,7 @@ def test__query_jobs_with_invalid_projection__raises_ApiException_BadRequest( with pytest.raises(ApiException, match="Bad Request"): client.query_jobs(query=query) - def test__query_jobs_order_by_created_timestamp_in_ascending_order__returns_jobs_sorted_by_created_timestamp_in_ascending_order( + def test__query_jobs_order_by_created_timestamp_in_asc__returns_jobs_sorted_by_created_timestamp_in_asc( self, client: SystemClient ): query = QueryJobsRequest(order_by=JobField.CREATED_TIMESTAMP, take=3) @@ -385,12 +381,17 @@ def test__query_jobs_order_by_created_timestamp_in_ascending_order__returns_jobs assert response.data is not None assert len(response.data) == response.count == 3 - assert all( - response.data[i].created_timestamp <= response.data[i + 1].created_timestamp - for i in range(len(response.data) - 1) + assert response.data[0].created_timestamp is not None + assert response.data[1].created_timestamp is not None + assert response.data[2].created_timestamp is not None + + assert ( + response.data[0].created_timestamp + <= response.data[1].created_timestamp + <= response.data[2].created_timestamp ) - def test__query_jobs_order_by_completing_timestamp_in_descending_order__returns_jobs_sorted_by_completing_timestamp_in_descending_order( + def test__query_jobs_order_by_completing_timestamp_in_desc__returns_jobs_sorted_by_completing_timestamp_in_desc( self, client: SystemClient ): query = QueryJobsRequest( @@ -402,10 +403,14 @@ def test__query_jobs_order_by_completing_timestamp_in_descending_order__returns_ assert response.data is not None assert len(response.data) == response.count == 3 - assert all( - response.data[i].completing_timestamp - >= response.data[i + 1].completing_timestamp - for i in range(len(response.data) - 1) + assert response.data[0].completing_timestamp is not None + assert response.data[1].completing_timestamp is not None + assert response.data[2].completing_timestamp is not None + + assert ( + response.data[0].completing_timestamp + >= response.data[1].completing_timestamp + >= response.data[2].completing_timestamp ) @responses.activate @@ -448,10 +453,44 @@ def test__cancel_with_invalid_jid_system_id__cancel_job_returns_None( assert cancel_response is None + @responses.activate def test__cancel_with_invalid_jid_valid_system_id__cancel_job_returns_error( self, client: SystemClient ): - cancel_job_request = CancelJobRequest(id="Invalid_jid", system_id=TARGET_SYSTEM) + return_value = { + "error": { + "name": "Skyline.OneOrMoreErrorsOccurred", + "code": -251041, + "message": "One or more errors occurred. See the contained list for details of each error.", + "args": [], + "innerErrors": [ + { + "name": "SystemsManagement.SaltCancelJobFailed", + "code": -254003, + "message": "The job is not found or you are not authorized to cancel it.", + "resourceType": "Job", + "resourceId": "54afefqf4b95-ea89-48df-b21f-dddrwerf56083476", + "args": [ + "54af4b95-ea89-48df-b21f-dddrfrewf56083476", + "ferfgertgw--SN-eger--rf-AC-1A-3D-99-75-3F", + ], + "innerErrors": [], + } + ], + } + } + + responses.add( + method=responses.POST, + url="https://dev-api.lifecyclesolutions.ni.com/nisysmgmt/v1/cancel-jobs", + json=return_value, + status=200, + ) + + cancel_job_request = CancelJobRequest( + id="Invalid_jid", + system_id="ferfgertgw--SN-eger--rf-AC-1A-3D-99-75-3F", + ) cancel_response = client.cancel_jobs([cancel_job_request]) - assert cancel_response is None + assert isinstance(cancel_response, ApiError) From 3e727c88c3c5a061d29a9e225724d1441182ab15 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 11 Dec 2024 22:52:58 +0530 Subject: [PATCH 22/23] refactor: modify union operator. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/models/_query_jobs_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nisystemlink/clients/system/models/_query_jobs_request.py b/nisystemlink/clients/system/models/_query_jobs_request.py index 9c370e88..4c627033 100644 --- a/nisystemlink/clients/system/models/_query_jobs_request.py +++ b/nisystemlink/clients/system/models/_query_jobs_request.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import List, Optional +from typing import List, Optional, Union from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -103,7 +103,7 @@ class QueryJobsRequest(JsonModel): Example: result.success.Contains(false) """ - projection: List[JobField | str] = [] + projection: List[Union[JobField, str]] = [] """ Gets or sets specifies the projection for resources. Use this field to receive specific properties of the model. From 6ebb8f16edc75a84ea15fc502595695e1c1141e9 Mon Sep 17 00:00:00 2001 From: Ganesh Nithin Date: Wed, 11 Dec 2024 23:17:02 +0530 Subject: [PATCH 23/23] refactor: format system client code. Signed-off-by: Ganesh Nithin --- nisystemlink/clients/system/_system_client.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nisystemlink/clients/system/_system_client.py b/nisystemlink/clients/system/_system_client.py index d1f154b5..1bd30785 100644 --- a/nisystemlink/clients/system/_system_client.py +++ b/nisystemlink/clients/system/_system_client.py @@ -150,8 +150,7 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse ApiException: if unable to communicate with the ``/nisysmgmt`` Service or provided an invalid argument. """ - projection = ",".join(query.projection) - projection = f"new({projection})" if projection else "" + projection = f"new({','.join(query.projection)})" if query.projection else None order_by = ( f"{query.order_by.value} {'descending' if query.descending else 'ascending'}" @@ -160,11 +159,11 @@ def query_jobs(self, query: models.QueryJobsRequest) -> models.QueryJobsResponse ) query_params = { - "skip": query.skip if query.skip is not None else None, - "take": query.take if query.take is not None else None, - "filter": query.filter if query.filter is not None else None, - "projection": projection, # If None, this will be omitted - "order_by": order_by, # If None, this will be omitted + "skip": query.skip, + "take": query.take, + "filter": query.filter, + "projection": projection, + "order_by": order_by, } # Clean the query_params to remove any keys with None values