Commit 4ac3aed2 authored by Waleed Akbar's avatar Waleed Akbar
Browse files

feat(pluggables): EOCO2026-Enhance JSON to proto conversion and RESTCONF path...

feat(pluggables): EOCO2026-Enhance JSON to proto conversion and RESTCONF path handling for pluggable indices
parent 6f2d6038
Loading
Loading
Loading
Loading
+57 −34
Original line number Diff line number Diff line
@@ -143,14 +143,24 @@ def json_to_configure_pluggable_request(


def _add_dsc_groups_from_hub_format(
    config: pluggables_pb2.DigitalSubcarrierGroupConfig,                                                                  # pyright: ignore[reportInvalidTypeForm]
    config: pluggables_pb2.PluggableConfig,                                                                               # pyright: ignore[reportInvalidTypeForm]
    device_uuid: str,
    pluggable_index: int,
    payload: Dict[str, Any]
) -> None:
    """
    Add DSC groups from HUB format JSON payload.
    Sets top-level PluggableConfig fields and DSC groups.
    """
    if "name" in payload:
        config.channel_name = payload["name"]  # type: ignore[attr-defined]

    if "frequency" in payload:
        config.center_frequency_mhz = float(payload["frequency"])  # type: ignore[attr-defined]

    if "target_output_power" in payload:
        config.target_output_power_dbm = float(payload["target_output_power"])  # type: ignore[attr-defined]

    dsc_groups = payload.get("digital_subcarriers_groups", [])
    
    for group_data in dsc_groups:
@@ -181,9 +191,9 @@ def _add_dsc_groups_from_hub_format(
            subcarrier.id.subcarrier_index                        = subcarrier_id                                         # type: ignore[attr-defined]
            subcarrier.active                                     = is_active                                             # type: ignore[attr-defined]
            
            # Set frequency and power from top-level payload
            # Set frequency (MHz from payload → Hz for subcarrier proto field)
            if "frequency" in payload:
                subcarrier.center_frequency_hz = float(payload["frequency"])                                              # type: ignore[attr-defined]
                subcarrier.center_frequency_hz = float(payload["frequency"]) * 1e6  # type: ignore[attr-defined]  # MHz to Hz
            
            if "target_output_power" in payload:
                subcarrier.target_output_power_dbm = float(payload["target_output_power"])                                # type: ignore[attr-defined]
@@ -193,47 +203,60 @@ def _add_dsc_groups_from_hub_format(


def _add_dsc_groups_from_leaf_format(
    config: pluggables_pb2.DigitalSubcarrierGroupConfig,                                                                 # pyright: ignore[reportInvalidTypeForm]
    config: pluggables_pb2.PluggableConfig,                                                                               # pyright: ignore[reportInvalidTypeForm]
    device_uuid: str,
    pluggable_index: int,
    payload: Dict[str, Any]
) -> None:
    """
    Add DSC groups from LEAF format JSON payload.
    Creates one PluggableChannelConfig per channel in the payload.
    """
    channels = payload.get("channels", [])

    for channel_idx, channel_data in enumerate(channels):
    for channel_data in channels:
        channel_config = config.channel_configs.add()                                                                     # type: ignore[attr-defined]

        if "name" in channel_data:
            channel_config.channel_name = channel_data["name"]  # type: ignore[attr-defined]

        if "frequency" in channel_data:
            channel_config.center_frequency_mhz = float(channel_data["frequency"])  # type: ignore[attr-defined]

        if "target_output_power" in channel_data:
            channel_config.target_output_power_dbm = float(channel_data["target_output_power"])  # type: ignore[attr-defined]

        dsc_groups = channel_data.get("digital_subcarriers_groups", [])

        for group_data in dsc_groups:
            group_id = group_data.get("group_id", channel_idx)
            group_id = group_data.get("group_id", 0)

            # Create DSC group (protobuf repeated field operations)
            dsc_group = config.dsc_groups.add()                                                                           # type: ignore[attr-defined]
            dsc_group = channel_config.dsc_groups.add()                                                                   # type: ignore[attr-defined]
            dsc_group.id.pluggable.device.device_uuid.uuid = device_uuid                                                  # type: ignore[attr-defined]
            dsc_group.id.pluggable.pluggable_index         = pluggable_index                                              # type: ignore[attr-defined]
            dsc_group.id.group_index                       = group_id                                                     # type: ignore[attr-defined]

            # Set group parameters
            dsc_group.group_capacity_gbps    = 400.0                                                                      # type: ignore[attr-defined]  # Default
            dsc_group.subcarrier_spacing_mhz = 75.0                                                                       # type: ignore[attr-defined]  # Default
            dsc_group.group_size              = 1                                                                         # type: ignore[attr-defined]  # Default for LEAF

            # Create a single subcarrier for this channel
            subcarrier_list      = group_data.get("digital-subcarrier-id", [])
            dsc_group.group_size = len(subcarrier_list)                                                                   # type: ignore[attr-defined]

            for subcarrier_data in subcarrier_list:
                subcarrier_id = subcarrier_data.get("subcarrier-id", 0)
                is_active     = subcarrier_data.get("active", False)

                subcarrier = dsc_group.subcarriers.add()                                                                  # type: ignore[attr-defined]
                subcarrier.id.group.pluggable.device.device_uuid.uuid = device_uuid                                       # type: ignore[attr-defined]
                subcarrier.id.group.pluggable.pluggable_index         = pluggable_index                                   # type: ignore[attr-defined]
                subcarrier.id.group.group_index                       = group_id                                          # type: ignore[attr-defined]
            subcarrier.id.subcarrier_index                        = 0                                                     # type: ignore[attr-defined]
            subcarrier.active                                     = True                                                  # type: ignore[attr-defined]  # Default for LEAF channels
                subcarrier.id.subcarrier_index                        = subcarrier_id                                     # type: ignore[attr-defined]
                subcarrier.active                                     = is_active                                         # type: ignore[attr-defined]

            # Set frequency and power from channel data
                if "frequency" in channel_data:
                subcarrier.center_frequency_hz = float(channel_data["frequency"])                                         # type: ignore[attr-defined]  # MHz to Hz
                    subcarrier.center_frequency_hz = float(channel_data["frequency"]) * 1e6  # type: ignore[attr-defined]  # MHz to Hz

                if "target_output_power" in channel_data:
                    subcarrier.target_output_power_dbm = float(channel_data["target_output_power"])  # type: ignore[attr-defined]

            # Default symbol rate
                subcarrier.symbol_rate_baud = 64000000000                                                                 # type: ignore[attr-defined]  # 64 GBaud
+47 −4
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
# limitations under the License.

import logging
import re
import grpc
from .enforce_header                           import require_accept, require_content_type
from .error                                    import _bad_request, _not_found, yang_json
@@ -41,6 +42,37 @@ blueprint = Blueprint("testconf_dscm", __name__)
YANG_JSON = "application/yang-data+json"
ERR_JSON  = "application/yang-errors+json"

# Regex to extract component=<key> from a RESTCONF path.
_COMPONENT_RE = re.compile(r"component=([^/]+)")


def _pluggable_index_from_restconf_path(rc_path: str) -> int:
    """Extract a pluggable index from a RESTCONF resource path.

    Parsing rules (in order):
      1. Match ``component=<key>`` segment in *rc_path*.
      2. If *<key>* is purely numeric, return ``int(key)``.
      3. If *<key>* matches ``channel-<number>``, return that number.
      4. Otherwise return ``-1`` (backward-compatible fallback).
    """
    match = _COMPONENT_RE.search(rc_path)
    if match is None:
        LOGGER.debug("No component=<key> found in rc_path=%r — fallback to -1", rc_path)
        return -1

    key = match.group(1)
    if key.isdigit():
        return int(key)

    channel_match = re.fullmatch(r"channel-(\d+)", key)
    if channel_match:
        return int(channel_match.group(1))

    LOGGER.debug(
        "Unsupported component key=%r in rc_path=%r — fallback to -1", key, rc_path
    )
    return -1


# Root endpoints (both prefixes) TODO: call list pluggables if device_uuid is given
# @blueprint.route("/device=<device_uuid>/", methods=["GET"])
@@ -61,8 +93,11 @@ def rc_get(rc_path, device_uuid=None):
        return _bad_request("Device UUID must be specified for GET requests.", path=rc_path)
    pluggables_client = PluggablesClient()

    pluggable_index = _pluggable_index_from_restconf_path(rc_path)
    LOGGER.info("Resolved pluggable_index=%d from rc_path=%r", pluggable_index, rc_path)

    try:
        get_request = json_to_get_pluggable_request(device_uuid)
        get_request = json_to_get_pluggable_request(device_uuid, pluggable_index=pluggable_index)
        pluggable = pluggables_client.GetPluggable(get_request)
        LOGGER.info(f"Successfully retrieved pluggable for device {device_uuid}")
        response_data = grpc_message_to_json(pluggable)
@@ -97,10 +132,14 @@ def rc_post(rc_path, device_uuid=None):
    if payload is None:
        return _bad_request("Invalid or empty JSON payload.", path=rc_path)
    
    pluggable_index = _pluggable_index_from_restconf_path(rc_path)
    LOGGER.info("Resolved pluggable_index=%d from rc_path=%r", pluggable_index, rc_path)

    try:
        create_request = json_to_create_pluggable_request(
            device_uuid             = device_uuid,
            initial_config          = payload,
            preferred_pluggable_index = pluggable_index,
        )
        
        pluggables_client = PluggablesClient()
@@ -142,9 +181,13 @@ def rc_delete(rc_path, device_uuid=None):
        return _bad_request("Device UUID must be specified for DELETE requests.", path=rc_path)
    
    pluggables_client = PluggablesClient()

    pluggable_index = _pluggable_index_from_restconf_path(rc_path)
    LOGGER.info("Resolved pluggable_index=%d from rc_path=%r", pluggable_index, rc_path)

    try:
        # Delete specific pluggable
        delete_request = json_to_delete_pluggable_request(device_uuid)
        delete_request = json_to_delete_pluggable_request(device_uuid, pluggable_index=pluggable_index)
        pluggables_client.DeletePluggable(delete_request)
        LOGGER.info(f"Successfully deleted pluggable for device {device_uuid}")
        return Response(status=204)