Skip to content

Commit

Permalink
[uniconfig] Update frinx-uniconfig-api to v1.0.0 and refactored unico…
Browse files Browse the repository at this point in the history
…nfig workers (#37)

* Enhanced uniconfig handle_response for frinx/uniconfig:6.0.0

* Refactor handle_response for Uniconfig and bump services-api to UC 6.0 to test compatibility

* Refactor DeviceDiscoveryWorker

* Updated frinx-uniconfig-api dependency to v1.0.0 and changelog

* Add validation for worker_output in handle_response

* Update package version in principles of semantic versioning
  • Loading branch information
MartinFoka authored Mar 14, 2024
1 parent 9fea11f commit b0a23d6
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 383 deletions.
4 changes: 4 additions & 0 deletions uniconfig/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@

# 1.0.4
- Enhanced 'handle_response' function for better error insights

# 2.0.0
- Updated API dependency frinx-uniconfig-api to rev "[frinx-uniconfig-api_v1.0.0](https://github.com/FRINXio/frinx-services-python-api/blob/main/uniconfig/python/CHANGELOG.md)"
- Simplified response handling from UniConfig and refactored DeviceDiscovery
59 changes: 32 additions & 27 deletions uniconfig/python/frinx_worker/uniconfig/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import dataclasses
import http.client
import json
from typing import Any
from typing import Optional

from frinx.common.conductor_enums import TaskResultStatus
from frinx.common.type_aliases import DictAny
from frinx.common.util import remove_empty_elements_from_dict
from frinx.common.worker.task_result import TO
from frinx.common.worker.task_result import TaskResult
from pydantic import BaseModel
from requests import Response
Expand All @@ -17,36 +17,41 @@ class TransactionContext(BaseModel):
transaction_id: str


def handle_response(response: Response, worker_output: Optional[TO] = None) -> TaskResult:
common_log_info = (
f"URL: {response.url}; "
f"HTTP Request Status: {response.status_code} - {response.reason}; "
f"Method: {response.request.method}; "
f"Response content: '{response.content.decode('utf-8', errors='replace')[:200]}' "
)

def failed_task_result(reason: str) -> TaskResult:
return TaskResult(
status=TaskResultStatus.FAILED,
logs=f'{reason}; {common_log_info}'
)

if not response.ok:
return failed_task_result(f'HTTP request failed with status code {response.status_code}')
def handle_response(response: Response, worker_output: Any) -> TaskResult:
"""
Handles an HTTP response from Uniconfig by logging the request details and processing the response content.
try:
if worker_output is not None:
worker_output.output = response.json()
output_status = worker_output.output.get('output', {}).get('status')
if output_status in ['fail','error']:
return failed_task_result('The response indicates failure')
Args:
response: The HTTP response from Uniconfig.
worker_output: Typically a callable Type[WorkerOutput]. It's common for this callable to be a method like
`self.WorkerOutput`, which can construct an instance of `WorkerOutput`.
except json.JSONDecodeError:
return failed_task_result('JSON decoding failed - unparsable response content')
Returns:
TaskResult: An object representing the result of the task. It includes the task status (COMPLETED on success,
FAILED otherwise), logs detailing the response, and the processed output.
"""
# Check if 'worker_output' has the 'model_fields' attribute and the 'output' field is present
if not hasattr(worker_output, 'model_fields') or 'output' not in worker_output.model_fields:
raise ValueError("The worker_output does not have the expected 'output' field. "
"Function handle_response expects WorkerOutput with a field named 'output'.")

output = dict()
status = TaskResultStatus.COMPLETED if response.ok else TaskResultStatus.FAILED
logs = (f'{response.request.method} request to {response.url} returned with status code {response.status_code}. '
f'CONTENT: {response.content.decode("utf-8")}')

# Attempt to parse response content as JSON if the response contains content.
if response.status_code != http.client.NO_CONTENT:
try:
output = response.json()
except json.JSONDecodeError:
logs += 'ERROR: JSON decoding failed - unparsable response content. '
status = TaskResultStatus.FAILED

return TaskResult(
status=TaskResultStatus.COMPLETED,
output=worker_output
status=status,
logs=logs,
output=worker_output(output=output)
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@


class CliNetworkTopology(ServiceWorkersImpl):

class ExecuteAndRead(WorkerImpl):

from frinx_api.uniconfig.cli.unit.generic.executeandread import Input
Expand Down Expand Up @@ -68,7 +67,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS,
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class Execute(WorkerImpl):

Expand Down Expand Up @@ -119,4 +118,4 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)
9 changes: 4 additions & 5 deletions uniconfig/python/frinx_worker/uniconfig/connection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class UninstallNode(WorkerImpl):
from frinx_api.uniconfig.connection.manager import ConnectionType
Expand Down Expand Up @@ -106,7 +106,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class InstallMultipleNodes(WorkerImpl):
from frinx_api.uniconfig.connection.manager.installmultiplenodes import Input
Expand Down Expand Up @@ -148,8 +148,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
),
headers=dict(UNICONFIG_HEADERS, accept='application/json')
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class UninstallMultipleNodes(WorkerImpl):
from frinx_api.uniconfig.connection.manager.uninstallmultiplenodes import Input
Expand Down Expand Up @@ -193,4 +192,4 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)
40 changes: 16 additions & 24 deletions uniconfig/python/frinx_worker/uniconfig/device_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import pydantic
import requests
from frinx.common.conductor_enums import TaskResultStatus
from frinx.common.frinx_rest import UNICONFIG_HEADERS
from frinx.common.frinx_rest import UNICONFIG_REQUEST_PARAMS
from frinx.common.frinx_rest import UNICONFIG_URL_BASE
from frinx.common.type_aliases import ListStr
from frinx.common.worker.service import ServiceWorkersImpl
Expand All @@ -24,6 +25,7 @@
from pydantic import IPvAnyNetwork

from . import class_to_json
from . import handle_response
from .util import get_list_of_ip_addresses
from .util import parse_ranges

Expand Down Expand Up @@ -70,12 +72,18 @@ def validate_ip(cls, ip: str) -> list[Address]:
return [address]

@pydantic.field_validator('tcp_port', mode='before')
def validate_tcp(cls, tcp_port: str) -> list[TcpPortItem]:
return [TcpPortItem(port=p) for p in parse_ranges(tcp_port.split(','))]
def validate_tcp(cls, tcp_port: str) -> list[TcpPortItem] | None:
if tcp_port:
return [TcpPortItem(port=p) for p in parse_ranges(tcp_port.split(','))]
else:
return None

@pydantic.field_validator('udp_port', mode='before')
def validate_udp(cls, udp_port: str) -> list[UdpPortItem]:
return [UdpPortItem(port=p) for p in parse_ranges(udp_port.split(','))]
def validate_udp(cls, udp_port: str) -> list[UdpPortItem] | None:
if udp_port:
return [UdpPortItem(port=p) for p in parse_ranges(udp_port.split(','))]
else:
return None

class WorkerOutput(TaskOutput):
output: OperationsDiscoverPostResponse
Expand All @@ -95,6 +103,8 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:

response = requests.request(
url=UNICONFIG_URL_BASE + Discover.uri,
headers=dict(UNICONFIG_HEADERS),
params=UNICONFIG_REQUEST_PARAMS,
method=Discover.method,
data=class_to_json(
Discover.request(
Expand All @@ -103,22 +113,4 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
),
)

if response.ok:
status = TaskResultStatus.COMPLETED

return TaskResult(
status=status,
output=self.WorkerOutput(
output=Discover.response(
output=response.json()['output']
)
)
)

status = TaskResultStatus.FAILED
return TaskResult(
status=status,
output=self.WorkerOutput(
output=response.json()
)
)
return handle_response(response, self.WorkerOutput)
6 changes: 3 additions & 3 deletions uniconfig/python/frinx_worker/uniconfig/snapshot_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class DeleteSnapshot(WorkerImpl):
from frinx_api.uniconfig.rest_api import DeleteSnapshot as UniconfigApi
Expand Down Expand Up @@ -110,7 +110,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class ReplaceConfigWithSnapshot(WorkerImpl):
from frinx_api.uniconfig.rest_api import ReplaceConfigWithSnapshot as UniconfigApi
Expand Down Expand Up @@ -158,4 +158,4 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)
20 changes: 3 additions & 17 deletions uniconfig/python/frinx_worker/uniconfig/structured_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class WriteStructuredData(WorkerImpl):
from frinx_api.uniconfig.rest_api import ReadStructuredData as UniconfigApi
Expand Down Expand Up @@ -123,14 +123,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(
response,
self.WorkerOutput(
output=dict(
response_code=response.status_code
)
)
)
return handle_response(response, self.WorkerOutput)

class DeleteStructuredData(WorkerImpl):
from frinx_api.uniconfig.rest_api import DeleteStructuredData as UniconfigApi
Expand Down Expand Up @@ -187,11 +180,4 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(
response,
self.WorkerOutput(
output=dict(
response_code=response.status_code
)
)
)
return handle_response(response, self.WorkerOutput)
8 changes: 4 additions & 4 deletions uniconfig/python/frinx_worker/uniconfig/uniconfig_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class ReplaceConfigWithOperational(WorkerImpl):
from frinx_api.uniconfig.rest_api import ReplaceConfigWithOperational as UniconfigApi
Expand Down Expand Up @@ -224,7 +224,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class SyncFromNetwork(WorkerImpl):
from frinx_api.uniconfig.rest_api import SyncFromNetwork as UniconfigApi
Expand Down Expand Up @@ -270,7 +270,7 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)

class DryRunCommit(WorkerImpl):
from frinx_api.uniconfig.dryrun.manager.dryruncommit import Input
Expand Down Expand Up @@ -320,4 +320,4 @@ def execute(self, worker_input: WorkerInput) -> TaskResult[WorkerOutput]:
params=UNICONFIG_REQUEST_PARAMS
)

return handle_response(response, self.WorkerOutput(output=dict()))
return handle_response(response, self.WorkerOutput)
Loading

0 comments on commit b0a23d6

Please sign in to comment.