Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[uniconfig] Update frinx-uniconfig-api to v1.0.0 and refactored uniconfig workers #37

Merged
merged 11 commits into from
Mar 14, 2024
Merged
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
Loading