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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions plugins/module_utils/nd_state_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@

from __future__ import absolute_import, division, print_function

from typing import Type, Union, List, Any, Callable, Optional
from typing import Any, Callable, List, Optional, Type, Union

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.nd.plugins.module_utils.nd import NDModule
from ansible_collections.cisco.nd.plugins.module_utils.nd_output import NDOutput
from ansible_collections.cisco.nd.plugins.module_utils.common.exceptions import NDStateMachineError
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.nd_config_collection import NDConfigCollection
from ansible_collections.cisco.nd.plugins.module_utils.nd_output import NDOutput
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType
from ansible_collections.cisco.nd.plugins.module_utils.common.exceptions import NDStateMachineError
from ansible_collections.cisco.nd.plugins.module_utils.rest.response_handler_nd import ResponseHandler
from ansible_collections.cisco.nd.plugins.module_utils.rest.rest_send import RestSend
from ansible_collections.cisco.nd.plugins.module_utils.rest.sender_nd import Sender


class NDStateMachine:
Expand All @@ -26,15 +29,27 @@ def __init__(self, module: AnsibleModule, model_orchestrator: Union[Type[NDBaseO
Initialize the ND State Machine.
"""
self.module = module
self.nd_module = NDModule(self.module)

# REST infrastructure
sender = Sender()
sender.ansible_module = self.module

self.rest_send = RestSend(
{
"check_mode": self.module.check_mode,
"state": self.module.params.get("state"),
}
)
self.rest_send.sender = sender
self.rest_send.response_handler = ResponseHandler()

# Operation tracking
self.output = NDOutput(output_level=module.params.get("output_level", "normal"))

# Configuration
# Accept either an orchestrator instance or a class.
if isinstance(model_orchestrator, type) and issubclass(model_orchestrator, NDBaseOrchestrator):
self.model_orchestrator = model_orchestrator(sender=self.nd_module)
self.model_orchestrator = model_orchestrator(rest_send=self.rest_send)
elif isinstance(model_orchestrator, NDBaseOrchestrator):
self.model_orchestrator = model_orchestrator
else:
Expand Down
76 changes: 66 additions & 10 deletions plugins/module_utils/orchestrators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
from __future__ import absolute_import, division, print_function

from functools import wraps
from typing import Any, ClassVar, Dict, Generic, List, Optional, Type, TypeVar

from ansible_collections.cisco.nd.plugins.module_utils.common.pydantic_compat import BaseModel, ConfigDict, model_validator
from typing import ClassVar, Type, Optional, Generic, TypeVar, List
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.nd import NDModule
from ansible_collections.cisco.nd.plugins.module_utils.endpoints.base import NDEndpointBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.enums import HttpVerbEnum
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType
from ansible_collections.cisco.nd.plugins.module_utils.rest.rest_send import RestSend

ModelType = TypeVar("ModelType", bound=NDBaseModel)

Expand Down Expand Up @@ -53,45 +55,99 @@ class NDBaseOrchestrator(BaseModel, Generic[ModelType]):
create_bulk_endpoint: Optional[Type[NDEndpointBaseModel]] = None
delete_bulk_endpoint: Optional[Type[NDEndpointBaseModel]] = None

# NOTE: Module Field is always required
sender: NDModule
# REST infrastructure
rest_send: RestSend

def _request(self, path: str, verb: HttpVerbEnum, data: Optional[Dict[str, Any]] = None) -> ResponseType:
"""
# Summary

Send a REST request via RestSend and return the response DATA.

## Raises

### Exception

- If the request fails (non-success result from the controller).
"""
self.rest_send.path = path
self.rest_send.verb = verb
if data is not None:
self.rest_send.payload = data
self.rest_send.commit()

result = self.rest_send.result_current
if not result.get("success", False):
response = self.rest_send.response_current
msg = response.get("MESSAGE", "Unknown error")
code = response.get("RETURN_CODE", -1)
raise Exception(f"Request failed ({code}): {msg}")

return self.rest_send.response_current.get("DATA", {})

def _query_obj(self, path: str) -> Dict[str, Any]:
"""
# Summary

GET the given path and return the DATA dict, or empty dict if not found.

## Raises

### Exception

- If the request fails with a non-404 error.
"""
self.rest_send.path = path
self.rest_send.verb = HttpVerbEnum.GET
self.rest_send.commit()

result = self.rest_send.result_current
if not result.get("success", False):
response = self.rest_send.response_current
if response.get("RETURN_CODE") == 404:
return {}
msg = response.get("MESSAGE", "Unknown error")
code = response.get("RETURN_CODE", -1)
raise Exception(f"Query failed ({code}): {msg}")

return self.rest_send.response_current.get("DATA", {})

# NOTE: Generic CRUD API operations for simple endpoints with single identifier (e.g. "api/v1/infra/aaa/LocalUsers/{loginID}")
def create(self, model_instance: ModelType, **kwargs) -> ResponseType:
try:
api_endpoint = self.create_endpoint()
return self.sender.request(path=api_endpoint.path, method=api_endpoint.verb, data=model_instance.to_payload())
return self._request(path=api_endpoint.path, verb=api_endpoint.verb, data=model_instance.to_payload())
except Exception as e:
raise Exception(f"Create failed for {model_instance.get_identifier_value()}: {e}") from e

def update(self, model_instance: ModelType, **kwargs) -> ResponseType:
try:
api_endpoint = self.update_endpoint()
api_endpoint.set_identifiers(model_instance.get_identifier_value())
return self.sender.request(path=api_endpoint.path, method=api_endpoint.verb, data=model_instance.to_payload())
return self._request(path=api_endpoint.path, verb=api_endpoint.verb, data=model_instance.to_payload())
except Exception as e:
raise Exception(f"Update failed for {model_instance.get_identifier_value()}: {e}") from e

def delete(self, model_instance: ModelType, **kwargs) -> ResponseType:
try:
api_endpoint = self.delete_endpoint()
api_endpoint.set_identifiers(model_instance.get_identifier_value())
return self.sender.request(path=api_endpoint.path, method=api_endpoint.verb)
return self._request(path=api_endpoint.path, verb=api_endpoint.verb)
except Exception as e:
raise Exception(f"Delete failed for {model_instance.get_identifier_value()}: {e}") from e

def query_one(self, model_instance: ModelType, **kwargs) -> ResponseType:
try:
api_endpoint = self.query_one_endpoint()
api_endpoint.set_identifiers(model_instance.get_identifier_value())
return self.sender.request(path=api_endpoint.path, method=api_endpoint.verb)
return self._request(path=api_endpoint.path, verb=api_endpoint.verb)
except Exception as e:
raise Exception(f"Query failed for {model_instance.get_identifier_value()}: {e}") from e

def query_all(self, model_instance: Optional[ModelType] = None, **kwargs) -> ResponseType:
try:
api_endpoint = self.query_all_endpoint()
result = self.sender.query_obj(api_endpoint.path)
result = self._query_obj(api_endpoint.path)
return result or []
except Exception as e:
raise Exception(f"Query all failed: {e}") from e
Expand Down
17 changes: 9 additions & 8 deletions plugins/module_utils/orchestrators/local_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

from __future__ import absolute_import, division, print_function

from typing import Type, ClassVar
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.local_user.local_user import LocalUserModel
from typing import ClassVar, Type

from ansible_collections.cisco.nd.plugins.module_utils.endpoints.base import NDEndpointBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType
from ansible_collections.cisco.nd.plugins.module_utils.endpoints.v1.infra.aaa_local_users import (
EpInfraAaaLocalUsersPost,
EpInfraAaaLocalUsersPut,
EpInfraAaaLocalUsersDelete,
EpInfraAaaLocalUsersGet,
EpInfraAaaLocalUsersPost,
EpInfraAaaLocalUsersPut,
)
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.local_user.local_user import LocalUserModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType


class LocalUserOrchestrator(NDBaseOrchestrator[LocalUserModel]):
Expand All @@ -33,7 +34,7 @@ def query_all(self) -> ResponseType:
"""
try:
api_endpoint = self.query_all_endpoint()
result = self.sender.query_obj(api_endpoint.path)
result = self._query_obj(api_endpoint.path)
return result.get("localusers", []) or []
except Exception as e:
raise Exception(f"Query all failed: {e}") from e
13 changes: 7 additions & 6 deletions plugins/module_utils/orchestrators/manage_fabric_ebgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@
__metaclass__ = type

from typing import Type
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_fabric.manage_fabric_ebgp import FabricEbgpModel

from ansible_collections.cisco.nd.plugins.module_utils.endpoints.base import NDEndpointBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType
from ansible_collections.cisco.nd.plugins.module_utils.endpoints.v1.manage.manage_fabrics import (
EpManageFabricsDelete,
EpManageFabricsGet,
EpManageFabricsListGet,
EpManageFabricsPost,
EpManageFabricsPut,
EpManageFabricsDelete,
)
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_fabric.manage_fabric_ebgp import FabricEbgpModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType


class ManageEbgpFabricOrchestrator(NDBaseOrchestrator):
Expand All @@ -39,7 +40,7 @@ def query_all(self) -> ResponseType:
"""
try:
api_endpoint = self.query_all_endpoint()
result = self.sender.query_obj(api_endpoint.path)
result = self._query_obj(api_endpoint.path)
fabrics = result.get("fabrics", []) or []
return [f for f in fabrics if f.get("management", {}).get("type") == "vxlanEbgp"]
except Exception as e:
Expand Down
13 changes: 7 additions & 6 deletions plugins/module_utils/orchestrators/manage_fabric_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@
__metaclass__ = type

from typing import Type
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_fabric.manage_fabric_external import FabricExternalConnectivityModel

from ansible_collections.cisco.nd.plugins.module_utils.endpoints.base import NDEndpointBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType
from ansible_collections.cisco.nd.plugins.module_utils.endpoints.v1.manage.manage_fabrics import (
EpManageFabricsDelete,
EpManageFabricsGet,
EpManageFabricsListGet,
EpManageFabricsPost,
EpManageFabricsPut,
EpManageFabricsDelete,
)
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_fabric.manage_fabric_external import FabricExternalConnectivityModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType


class ManageExternalFabricOrchestrator(NDBaseOrchestrator):
Expand All @@ -39,7 +40,7 @@ def query_all(self) -> ResponseType:
"""
try:
api_endpoint = self.query_all_endpoint()
result = self.sender.query_obj(api_endpoint.path)
result = self._query_obj(api_endpoint.path)
fabrics = result.get("fabrics", []) or []
return [f for f in fabrics if f.get("management", {}).get("type") == "externalConnectivity"]
except Exception as e:
Expand Down
13 changes: 7 additions & 6 deletions plugins/module_utils/orchestrators/manage_fabric_ibgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@
__metaclass__ = type

from typing import Type
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_fabric.manage_fabric_ibgp import FabricIbgpModel

from ansible_collections.cisco.nd.plugins.module_utils.endpoints.base import NDEndpointBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType
from ansible_collections.cisco.nd.plugins.module_utils.endpoints.v1.manage.manage_fabrics import (
EpManageFabricsDelete,
EpManageFabricsGet,
EpManageFabricsListGet,
EpManageFabricsPost,
EpManageFabricsPut,
EpManageFabricsDelete,
)
from ansible_collections.cisco.nd.plugins.module_utils.models.base import NDBaseModel
from ansible_collections.cisco.nd.plugins.module_utils.models.manage_fabric.manage_fabric_ibgp import FabricIbgpModel
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.base import NDBaseOrchestrator
from ansible_collections.cisco.nd.plugins.module_utils.orchestrators.types import ResponseType


class ManageIbgpFabricOrchestrator(NDBaseOrchestrator):
Expand All @@ -39,7 +40,7 @@ def query_all(self) -> ResponseType:
"""
try:
api_endpoint = self.query_all_endpoint()
result = self.sender.query_obj(api_endpoint.path)
result = self._query_obj(api_endpoint.path)
fabrics = result.get("fabrics", []) or []
return [f for f in fabrics if f.get("management", {}).get("type") == "vxlanIbgp"]
except Exception as e:
Expand Down
Loading