Skip to content

Nautobot App Livedata API Package

nautobot_app_livedata.api

REST API module for Nautobot App Livedata.

serializers

Nautobot App Livedata API Serializers.

LivedataJobResultSerializer

Bases: Serializer

Serializer for the Nautobot App Livedata API to return the Job Result.

This serializer is used to get the job result for the given jobresult_id.

Properties:

  • jobresult_id (UUID): The job result ID of the job that was enqueued.

Raises: - ValidationError: If the job result ID is not defined. - ValidationError: If the job result is not found.

Source code in nautobot_app_livedata/api/serializers.py
class LivedataJobResultSerializer(serializers.Serializer):
    """Serializer for the Nautobot App Livedata API to return the Job Result.

    This serializer is used to get the job result for the given jobresult_id.

    Properties:

    - jobresult_id (UUID): The job result ID of the job that was enqueued.

    Raises:
    - ValidationError: If the job result ID is not defined.
    - ValidationError: If the job result is not found.

    """

    jobresult_id = serializers.UUIDField(required=True, allow_null=False)

    def validate(self, attrs):
        """Validate the job result ID."""
        if "jobresult_id" not in attrs:
            raise serializers.ValidationError("The job result ID is not defined", code="invalid")
        return attrs

    def create(self, validated_data):
        """Serializer does not create any objects."""
        raise NotImplementedError

    def update(self, instance, validated_data):
        """Serializer does not update any objects."""
        raise NotImplementedError
create(validated_data)

Serializer does not create any objects.

Source code in nautobot_app_livedata/api/serializers.py
def create(self, validated_data):
    """Serializer does not create any objects."""
    raise NotImplementedError
update(instance, validated_data)

Serializer does not update any objects.

Source code in nautobot_app_livedata/api/serializers.py
def update(self, instance, validated_data):
    """Serializer does not update any objects."""
    raise NotImplementedError
validate(attrs)

Validate the job result ID.

Source code in nautobot_app_livedata/api/serializers.py
def validate(self, attrs):
    """Validate the job result ID."""
    if "jobresult_id" not in attrs:
        raise serializers.ValidationError("The job result ID is not defined", code="invalid")
    return attrs

LivedataSerializer

Bases: Serializer

Serializer for the Nautobot App Livedata API to return the Managed Device.

For more information on implementing jobs, refer to the Nautobot job documentation: https://docs.nautobot.com/projects/core/en/stable/development/jobs/

This serializer is used to get the managed device for the given object_type and ID.

Properties:

  • pk (UUID): The primary key of the object.
  • object_type (str): The object type to get the managed device for.

Raises: - ValidationError: If the object type is not defined. - ValidationError: If the object ID is not defined. - ValidationError: If the object_type is not valid. - ValidationError: If the object is not found. - ValidationError: If the object does not have a primary IP address. - ValidationError: If the object state is not Active.

Source code in nautobot_app_livedata/api/serializers.py
class LivedataSerializer(serializers.Serializer):
    """Serializer for the Nautobot App Livedata API to return the Managed Device.

    For more information on implementing jobs, refer to the Nautobot job documentation:
    https://docs.nautobot.com/projects/core/en/stable/development/jobs/

    This serializer is used to get the managed device for the given object_type
    and ID.

    Properties:

    - pk (UUID): The primary key of the object.
    - object_type (str): The object type to get the managed device for.

    Raises:
    - ValidationError: If the object type is not defined.
    - ValidationError: If the object ID is not defined.
    - ValidationError: If the object_type is not valid.
    - ValidationError: If the object is not found.
    - ValidationError: If the object does not have a primary IP address.
    - ValidationError: If the object state is not Active.
    """

    pk = serializers.UUIDField(required=True, allow_null=False)
    object_type = serializers.CharField(required=True, allow_blank=False)

    queryset = Interface.objects.all()

    def validate(self, attrs):
        """Validate the object type and the device/interface ID."""
        # Check that the object_type is valid
        if "object_type" not in attrs:
            raise serializers.ValidationError("The object type is not defined", code="invalid")
        if "pk" not in attrs:
            raise serializers.ValidationError("The object ID is not defined", code="invalid")
        try:
            result = PrimaryDeviceUtils(object_type=attrs["object_type"], pk=attrs["pk"]).to_dict()
            attrs.update(result)
        except ValueError as err:
            raise serializers.ValidationError(str(err), code="invalid") from err
        return attrs

    def create(self, validated_data):
        """Serializer does not create any objects."""
        raise NotImplementedError

    def update(self, instance, validated_data):
        """Serializer does not update any objects."""
        raise NotImplementedError
create(validated_data)

Serializer does not create any objects.

Source code in nautobot_app_livedata/api/serializers.py
def create(self, validated_data):
    """Serializer does not create any objects."""
    raise NotImplementedError
update(instance, validated_data)

Serializer does not update any objects.

Source code in nautobot_app_livedata/api/serializers.py
def update(self, instance, validated_data):
    """Serializer does not update any objects."""
    raise NotImplementedError
validate(attrs)

Validate the object type and the device/interface ID.

Source code in nautobot_app_livedata/api/serializers.py
def validate(self, attrs):
    """Validate the object type and the device/interface ID."""
    # Check that the object_type is valid
    if "object_type" not in attrs:
        raise serializers.ValidationError("The object type is not defined", code="invalid")
    if "pk" not in attrs:
        raise serializers.ValidationError("The object ID is not defined", code="invalid")
    try:
        result = PrimaryDeviceUtils(object_type=attrs["object_type"], pk=attrs["pk"]).to_dict()
        attrs.update(result)
    except ValueError as err:
        raise serializers.ValidationError(str(err), code="invalid") from err
    return attrs

urls

Nautobot App Livedata API URLs.

views

Views for the Livedata API.

LivedataPrimaryDeviceApiView

Bases: ObjectPermissionRequiredMixin, GenericAPIView

Nautobot App Livedata API Primary Device view.

For more information on implementing jobs, refer to the Nautobot job documentation: https://docs.nautobot.com/projects/core/en/stable/development/jobs/

Source code in nautobot_app_livedata/api/views.py
class LivedataPrimaryDeviceApiView(ObjectPermissionRequiredMixin, GenericAPIView):
    """Nautobot App Livedata API Primary Device view.

    For more information on implementing jobs, refer to the Nautobot job documentation:
    https://docs.nautobot.com/projects/core/en/stable/development/jobs/
    """

    serializer_class = LivedataSerializer
    queryset = Device.objects.all()

    def get_required_permission(self) -> str:
        """Get the required permission for the view.

        Format: app_label.action_model

        Returns:
            str: The permission required to access the view.
        """
        return "dcim.can_interact_device"

    def get(
        self, request: Any, *args: Any, pk: Optional[int] = None, object_type: Optional[str] = None, **kwargs: Any
    ) -> Response:
        """Handle GET request for Livedata Primary Device API.

        Args:
            request (HttpRequest): The request object.
            pk: The primary key of the object.
            object_type (str): The object type.
            *args: Additional positional arguments.
            **kwargs: Additional keyword arguments.

        Returns:
            Response: The response object. "application/json"

                data = {
                    "object_type": "The object type to get the primary device for",
                    "pk": "The primary key",
                    "device": "The device ID of the device that is referred in object_type",
                    "interface": "The interface ID if the object type is 'dcim.interface'",
                    "virtual_chassis": "The virtual chassis ID if the object type is 'dcim.virtualchassis'",
                    "primary_device": "The primary device ID"
                }

        Raises:
            Response: If the user does not have permission to execute 'livedata' on an interface.
            Response: If the serializer is not valid.
            Response: If the primary device is not found.
        """
        data = request.data
        data["pk"] = pk
        data["object_type"] = object_type
        serializer = self.get_serializer(data=request.data)
        if not serializer.is_valid():
            # Return error response if serializer is not valid
            return Response(
                {
                    "error": "Invalid data provided",
                    "details": serializer.errors,
                },
                status=HTTPStatus.BAD_REQUEST,  # 400
            )
        try:
            result = serializer.validated_data
        except ValueError as error:
            return Response(
                f"Failed to get primary device: {error}",
                status=HTTPStatus.BAD_REQUEST,  # 400
            )
        return Response(data=result, status=HTTPStatus.OK)  # 200
get(request, *args, pk=None, object_type=None, **kwargs)

Handle GET request for Livedata Primary Device API.

Parameters:

Name Type Description Default
request HttpRequest

The request object.

required
pk Optional[int]

The primary key of the object.

None
object_type str

The object type.

None
*args Any

Additional positional arguments.

()
**kwargs Any

Additional keyword arguments.

{}

Returns:

Name Type Description
Response Response

The response object. "application/json"

data = { "object_type": "The object type to get the primary device for", "pk": "The primary key", "device": "The device ID of the device that is referred in object_type", "interface": "The interface ID if the object type is 'dcim.interface'", "virtual_chassis": "The virtual chassis ID if the object type is 'dcim.virtualchassis'", "primary_device": "The primary device ID" }

Raises:

Type Description
Response

If the user does not have permission to execute 'livedata' on an interface.

Response

If the serializer is not valid.

Response

If the primary device is not found.

Source code in nautobot_app_livedata/api/views.py
def get(
    self, request: Any, *args: Any, pk: Optional[int] = None, object_type: Optional[str] = None, **kwargs: Any
) -> Response:
    """Handle GET request for Livedata Primary Device API.

    Args:
        request (HttpRequest): The request object.
        pk: The primary key of the object.
        object_type (str): The object type.
        *args: Additional positional arguments.
        **kwargs: Additional keyword arguments.

    Returns:
        Response: The response object. "application/json"

            data = {
                "object_type": "The object type to get the primary device for",
                "pk": "The primary key",
                "device": "The device ID of the device that is referred in object_type",
                "interface": "The interface ID if the object type is 'dcim.interface'",
                "virtual_chassis": "The virtual chassis ID if the object type is 'dcim.virtualchassis'",
                "primary_device": "The primary device ID"
            }

    Raises:
        Response: If the user does not have permission to execute 'livedata' on an interface.
        Response: If the serializer is not valid.
        Response: If the primary device is not found.
    """
    data = request.data
    data["pk"] = pk
    data["object_type"] = object_type
    serializer = self.get_serializer(data=request.data)
    if not serializer.is_valid():
        # Return error response if serializer is not valid
        return Response(
            {
                "error": "Invalid data provided",
                "details": serializer.errors,
            },
            status=HTTPStatus.BAD_REQUEST,  # 400
        )
    try:
        result = serializer.validated_data
    except ValueError as error:
        return Response(
            f"Failed to get primary device: {error}",
            status=HTTPStatus.BAD_REQUEST,  # 400
        )
    return Response(data=result, status=HTTPStatus.OK)  # 200
get_required_permission()

Get the required permission for the view.

Format: app_label.action_model

Returns:

Name Type Description
str str

The permission required to access the view.

Source code in nautobot_app_livedata/api/views.py
def get_required_permission(self) -> str:
    """Get the required permission for the view.

    Format: app_label.action_model

    Returns:
        str: The permission required to access the view.
    """
    return "dcim.can_interact_device"

LivedataQueryApiView

Bases: ObjectPermissionRequiredMixin, GenericAPIView, ABC

Abstract Livedata Query API view.

Source code in nautobot_app_livedata/api/views.py
class LivedataQueryApiView(ObjectPermissionRequiredMixin, GenericAPIView, ABC):
    """Abstract Livedata Query API view."""

    serializer_class = LivedataSerializer

    @abstractmethod
    def get_object_type(self) -> str:
        """Get the object type for the view.

        Returns:
            str: The object type.
        """

    @abstractmethod
    def get_commands(self, instance) -> list:
        """Get the commands to be executed for the given instance.

        Args:
            instance (Model): The model instance.

        Returns:
            list: The commands to be executed.
        """

    def get_required_permission(self) -> str:
        """Get the required permission for the view.

        Format: app_label.action_model

        Returns:
            str: The permission required to access the view.
        """
        return "dcim.can_interact_device"

    def get(self, request: Any, *args: Any, pk=None, **kwargs: Any) -> Response:
        """Handle GET request for Livedata Query API.

        The get method is used to enqueue the Livedata Query Job.

        To access the JobResult object, use the jobresult_id returned in the response
        and make a GET request to the JobResult endpoint.

        For Example:
            GET /api/extras/job-results/{jobresult_id}/

        Args:
            request (Request): The request object.
            pk (uuid): The primary key of the model instance.
            *args: Additional positional arguments.
            **kwargs: Additional keyword arguments.

        Returns:
            jobresult_id: The job result ID of the job that was enqueued.

        Raises:
            Response: If the user does not have permission to execute 'livedata' on the instance.
            Response: If the serializer is not valid.
            Response: If the job Livedata Api-Job is not found.
            Response: If the job failed to run.
        """
        data = request.data
        data["pk"] = pk
        data["object_type"] = self.get_object_type()
        serializer = self.get_serializer(data=data)
        if not serializer.is_valid():
            return Response(
                serializer.errors,
                status=HTTPStatus.BAD_REQUEST,  # 400
            )
        try:
            primary_device_info = serializer.validated_data
            instance = self.get_queryset().get(pk=pk)
            show_commands_j2_array = self.get_commands(instance)
        except (ValueError, self.get_queryset().model.DoesNotExist) as error:
            logger.error("Error during Livedata Query API: %s", error)
            status = HTTPStatus.BAD_REQUEST if isinstance(error, ValueError) else HTTPStatus.NOT_FOUND
            return Response(
                "An error occurred during the Livedata Query API request.",
                status=status,
            )

        job = Job.objects.filter(name=PLUGIN_SETTINGS["query_job_name"]).first()
        if job is None:
            return Response(
                f"{PLUGIN_SETTINGS['query_job_name']} not found",
                status=HTTPStatus.NOT_FOUND,  # 404
            )

        job_kwargs = {
            "commands_j2": show_commands_j2_array,
            "device_id": primary_device_info["device"],
            "interface_id": primary_device_info.get("interface"),
            "primary_device_id": primary_device_info["primary_device"],
            "remote_addr": request.META.get("REMOTE_ADDR"),
            "virtual_chassis_id": primary_device_info.get("virtual_chassis"),
            "x_forwarded_for": request.META.get("HTTP_X_FORWARDED_FOR"),
            "call_object_type": data["object_type"],
        }

        try:
            jobres = JobResult.enqueue_job(
                job,
                user=request.user,
                task_queue=PLUGIN_SETTINGS["query_job_task_queue"],
                **job_kwargs,
            )

            return Response(
                content_type="application/json",
                data={"jobresult_id": jobres.id},
                status=HTTPStatus.OK,  # 200
            )
        except RunJobTaskFailed as error:
            logger.error("Failed to run %s: %s", PLUGIN_SETTINGS["query_job_name"], error)

            return Response(
                "An internal error has occurred while running the job.",
                status=HTTPStatus.INTERNAL_SERVER_ERROR,  # 500
            )
get(request, *args, pk=None, **kwargs)

Handle GET request for Livedata Query API.

The get method is used to enqueue the Livedata Query Job.

To access the JobResult object, use the jobresult_id returned in the response and make a GET request to the JobResult endpoint.

For Example

GET /api/extras/job-results/{jobresult_id}/

Parameters:

Name Type Description Default
request Request

The request object.

required
pk uuid

The primary key of the model instance.

None
*args Any

Additional positional arguments.

()
**kwargs Any

Additional keyword arguments.

{}

Returns:

Name Type Description
jobresult_id Response

The job result ID of the job that was enqueued.

Raises:

Type Description
Response

If the user does not have permission to execute 'livedata' on the instance.

Response

If the serializer is not valid.

Response

If the job Livedata Api-Job is not found.

Response

If the job failed to run.

Source code in nautobot_app_livedata/api/views.py
def get(self, request: Any, *args: Any, pk=None, **kwargs: Any) -> Response:
    """Handle GET request for Livedata Query API.

    The get method is used to enqueue the Livedata Query Job.

    To access the JobResult object, use the jobresult_id returned in the response
    and make a GET request to the JobResult endpoint.

    For Example:
        GET /api/extras/job-results/{jobresult_id}/

    Args:
        request (Request): The request object.
        pk (uuid): The primary key of the model instance.
        *args: Additional positional arguments.
        **kwargs: Additional keyword arguments.

    Returns:
        jobresult_id: The job result ID of the job that was enqueued.

    Raises:
        Response: If the user does not have permission to execute 'livedata' on the instance.
        Response: If the serializer is not valid.
        Response: If the job Livedata Api-Job is not found.
        Response: If the job failed to run.
    """
    data = request.data
    data["pk"] = pk
    data["object_type"] = self.get_object_type()
    serializer = self.get_serializer(data=data)
    if not serializer.is_valid():
        return Response(
            serializer.errors,
            status=HTTPStatus.BAD_REQUEST,  # 400
        )
    try:
        primary_device_info = serializer.validated_data
        instance = self.get_queryset().get(pk=pk)
        show_commands_j2_array = self.get_commands(instance)
    except (ValueError, self.get_queryset().model.DoesNotExist) as error:
        logger.error("Error during Livedata Query API: %s", error)
        status = HTTPStatus.BAD_REQUEST if isinstance(error, ValueError) else HTTPStatus.NOT_FOUND
        return Response(
            "An error occurred during the Livedata Query API request.",
            status=status,
        )

    job = Job.objects.filter(name=PLUGIN_SETTINGS["query_job_name"]).first()
    if job is None:
        return Response(
            f"{PLUGIN_SETTINGS['query_job_name']} not found",
            status=HTTPStatus.NOT_FOUND,  # 404
        )

    job_kwargs = {
        "commands_j2": show_commands_j2_array,
        "device_id": primary_device_info["device"],
        "interface_id": primary_device_info.get("interface"),
        "primary_device_id": primary_device_info["primary_device"],
        "remote_addr": request.META.get("REMOTE_ADDR"),
        "virtual_chassis_id": primary_device_info.get("virtual_chassis"),
        "x_forwarded_for": request.META.get("HTTP_X_FORWARDED_FOR"),
        "call_object_type": data["object_type"],
    }

    try:
        jobres = JobResult.enqueue_job(
            job,
            user=request.user,
            task_queue=PLUGIN_SETTINGS["query_job_task_queue"],
            **job_kwargs,
        )

        return Response(
            content_type="application/json",
            data={"jobresult_id": jobres.id},
            status=HTTPStatus.OK,  # 200
        )
    except RunJobTaskFailed as error:
        logger.error("Failed to run %s: %s", PLUGIN_SETTINGS["query_job_name"], error)

        return Response(
            "An internal error has occurred while running the job.",
            status=HTTPStatus.INTERNAL_SERVER_ERROR,  # 500
        )
get_commands(instance) abstractmethod

Get the commands to be executed for the given instance.

Parameters:

Name Type Description Default
instance Model

The model instance.

required

Returns:

Name Type Description
list list

The commands to be executed.

Source code in nautobot_app_livedata/api/views.py
@abstractmethod
def get_commands(self, instance) -> list:
    """Get the commands to be executed for the given instance.

    Args:
        instance (Model): The model instance.

    Returns:
        list: The commands to be executed.
    """
get_object_type() abstractmethod

Get the object type for the view.

Returns:

Name Type Description
str str

The object type.

Source code in nautobot_app_livedata/api/views.py
@abstractmethod
def get_object_type(self) -> str:
    """Get the object type for the view.

    Returns:
        str: The object type.
    """
get_required_permission()

Get the required permission for the view.

Format: app_label.action_model

Returns:

Name Type Description
str str

The permission required to access the view.

Source code in nautobot_app_livedata/api/views.py
def get_required_permission(self) -> str:
    """Get the required permission for the view.

    Format: app_label.action_model

    Returns:
        str: The permission required to access the view.
    """
    return "dcim.can_interact_device"

LivedataQueryDeviceApiView

Bases: LivedataQueryApiView

Livedata Query Device API view.

Source code in nautobot_app_livedata/api/views.py
class LivedataQueryDeviceApiView(LivedataQueryApiView):
    """Livedata Query Device API view."""

    serializer_class = LivedataSerializer
    queryset = Device.objects.all()

    def get_object_type(self) -> str:
        """Get the object type for the view.

        Returns:
            str: The object type.
        """
        return "dcim.device"

    def get_commands(self, instance) -> list:
        """Get the commands to be executed for the given device.

        Args:
            instance (Device): The device instance.

        Returns:
            list: The commands to be executed.
        """
        return get_livedata_commands_for_device(instance)
get_commands(instance)

Get the commands to be executed for the given device.

Parameters:

Name Type Description Default
instance Device

The device instance.

required

Returns:

Name Type Description
list list

The commands to be executed.

Source code in nautobot_app_livedata/api/views.py
def get_commands(self, instance) -> list:
    """Get the commands to be executed for the given device.

    Args:
        instance (Device): The device instance.

    Returns:
        list: The commands to be executed.
    """
    return get_livedata_commands_for_device(instance)
get_object_type()

Get the object type for the view.

Returns:

Name Type Description
str str

The object type.

Source code in nautobot_app_livedata/api/views.py
def get_object_type(self) -> str:
    """Get the object type for the view.

    Returns:
        str: The object type.
    """
    return "dcim.device"

LivedataQueryInterfaceApiView

Bases: LivedataQueryApiView

Livedata Query Interface API view.

Source code in nautobot_app_livedata/api/views.py
class LivedataQueryInterfaceApiView(LivedataQueryApiView):
    """Livedata Query Interface API view."""

    serializer_class = LivedataSerializer
    queryset = Interface.objects.all()

    def get_object_type(self) -> str:
        """Get the object type for the view.

        Returns:
            str: The object type.
        """
        return "dcim.interface"

    def get_commands(self, instance) -> list:
        """Get the commands to be executed for the given interface.

        Args:
            instance (Interface): The interface instance.

        Returns:
            list: The commands to be executed.
        """
        return get_livedata_commands_for_interface(instance)
get_commands(instance)

Get the commands to be executed for the given interface.

Parameters:

Name Type Description Default
instance Interface

The interface instance.

required

Returns:

Name Type Description
list list

The commands to be executed.

Source code in nautobot_app_livedata/api/views.py
def get_commands(self, instance) -> list:
    """Get the commands to be executed for the given interface.

    Args:
        instance (Interface): The interface instance.

    Returns:
        list: The commands to be executed.
    """
    return get_livedata_commands_for_interface(instance)
get_object_type()

Get the object type for the view.

Returns:

Name Type Description
str str

The object type.

Source code in nautobot_app_livedata/api/views.py
def get_object_type(self) -> str:
    """Get the object type for the view.

    Returns:
        str: The object type.
    """
    return "dcim.interface"