Commit 3b03049a authored by Mohamad Rahhal's avatar Mohamad Rahhal
Browse files

Spine-Leaf:

-cleaned some unuseful tests
parent 69bc89a7
Loading
Loading
Loading
Loading
+0 −149
Original line number Diff line number Diff line
{
  "ietf-l3vpn-svc:l3vpn-svc": {
    "vpn-services": {
      "vpn-service": [
        {
          "vpn-id": "evpn-vxlan-vni10-leaf1-leaf4"
        }
      ]
    },
    "sites": {
      "site": [
        {
          "site-id": "site_MikroTik-5-Leaf1",
          "management": {
            "type": "ietf-l3vpn-svc:provider-managed"
          },
          "locations": {
            "location": [
              {
                "location-id": "MikroTik-5-Leaf1"
              }
            ]
          },
          "devices": {
            "device": [
              {
                "device-id": "6ac9614b-6893-549d-b39c-d4af4320678a",
                "location": "MikroTik-5-Leaf1"
              }
            ]
          },
          "site-network-accesses": {
            "site-network-access": [
              {
                "site-network-access-id": "sfp-sfpplus7",
                "site-network-access-type": "ietf-l3vpn-svc:multipoint",
                "device-reference": "6ac9614b-6893-549d-b39c-d4af4320678a",
                "vpn-attachment": {
                  "vpn-id": "evpn-vxlan-vni10-leaf1-leaf4",
                  "site-role": "ietf-l3vpn-svc:hub-role"
                },
                "ip-connection": {
                  "ipv4": {
                    "address-allocation-type": "ietf-l3vpn-svc:static-address",
                    "addresses": {
                      "provider-address": "10.1.7.215",
                      "customer-address": "10.1.7.215",
                      "prefix-length": 24
                    }
                  }
                },
                "service": {
                  "svc-mtu": 1500,
                  "svc-input-bandwidth": 1000000000,
                  "svc-output-bandwidth": 1000000000,
                  "qos": {
                    "qos-profile": {
                      "classes": {
                        "class": [
                          {
                            "class-id": "qos-realtime",
                            "direction": "ietf-l3vpn-svc:both",
                            "latency": {
                              "latency-boundary": 10
                            },
                            "bandwidth": {
                              "guaranteed-bw-percent": 100
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "site-id": "site_MikroTik-8-Leaf4",
          "management": {
            "type": "ietf-l3vpn-svc:provider-managed"
          },
          "locations": {
            "location": [
              {
                "location-id": "MikroTik-8-Leaf4"
              }
            ]
          },
          "devices": {
            "device": [
              {
                "device-id": "03d3c087-da28-539e-aae0-60da7b90bad6",
                "location": "MikroTik-8-Leaf4"
              }
            ]
          },
          "site-network-accesses": {
            "site-network-access": [
              {
                "site-network-access-id": "sfp-sfpplus7",
                "site-network-access-type": "ietf-l3vpn-svc:multipoint",
                "device-reference": "03d3c087-da28-539e-aae0-60da7b90bad6",
                "vpn-attachment": {
                  "vpn-id": "evpn-vxlan-vni10-leaf1-leaf4",
                  "site-role": "ietf-l3vpn-svc:spoke-role"
                },
                "ip-connection": {
                  "ipv4": {
                    "address-allocation-type": "ietf-l3vpn-svc:static-address",
                    "addresses": {
                      "provider-address": "10.1.7.218",
                      "customer-address": "10.1.7.218",
                      "prefix-length": 24
                    }
                  }
                },
                "service": {
                  "svc-mtu": 1500,
                  "svc-input-bandwidth": 1000000000,
                  "svc-output-bandwidth": 1000000000,
                  "qos": {
                    "qos-profile": {
                      "classes": {
                        "class": [
                          {
                            "class-id": "qos-realtime",
                            "direction": "ietf-l3vpn-svc:both",
                            "latency": {
                              "latency-boundary": 10
                            },
                            "bandwidth": {
                              "guaranteed-bw-percent": 100
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}
+0 −213
Original line number Diff line number Diff line
# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os
import copy

from common.proto.context_pb2 import ConfigRule
from common.proto.spineleaf_pb2 import DeployDeviceConfigRequest, UnsetDeviceRoleRequest
from common.tools.context_queries.Device import get_device
from common.tools.object_factory.ConfigRule import (
    json_config_rule_set,
    json_config_rule_delete,
)
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from spine_leaf.client.SpineLeafClient import SpineLeafClient

logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger("leaf_underlay_apply")


# -----------------------------
# ENV
# -----------------------------
CONTEXTSERVICE_HOST = os.getenv("CONTEXTSERVICE_SERVICE_HOST", "10.152.183.46")
CONTEXTSERVICE_PORT = int(os.getenv("CONTEXTSERVICE_SERVICE_PORT_GRPC", "1010"))

DEVICESERVICE_HOST = os.getenv("DEVICESERVICE_SERVICE_HOST", "10.152.183.102")
DEVICESERVICE_PORT = int(os.getenv("DEVICESERVICE_SERVICE_PORT_GRPC", "2020"))

SPINE_LEAFSERVICE_HOST = os.getenv("SPINE_LEAFSERVICE_SERVICE_HOST", "10.152.183.147")
SPINE_LEAFSERVICE_PORT = int(os.getenv("SPINE_LEAFSERVICE_SERVICE_PORT_GRPC", "10095"))

FABRIC_ID = os.getenv("FABRIC_ID", "354a08b7-3730-5dd6-9472-47554115818b")


# -----------------------------
# LEAF DEVICES ONLY
# -----------------------------
LEAF_DEVICE_UUIDS = [
    "1761a9f5-2672-5996-b3f3-3e91eaef20dc",
    "03d3c087-da28-539e-aae0-60da7b90bad6",
    "6ac9614b-6893-549d-b39c-d4af4320678a",
    "dc0d2a74-3415-5a4c-ac65-0f1ef1ca769b",
]


# -----------------------------
# FLAGS
# -----------------------------
def is_apply_enabled() -> bool:
    return os.getenv("APPLY_CONFIG", "false").lower() in {"1", "true", "yes"}


def is_deconfigure_enabled() -> bool:
    return os.getenv("DECONFIGURE", "false").lower() in {"1", "true", "yes"}


# -----------------------------
# PARSER
# -----------------------------
def parse_rule(entry):
    if not isinstance(entry, (list, tuple)) or len(entry) != 2:
        return None

    key, value = entry

    if isinstance(value, str):
        try:
            value = json.loads(value)
        except Exception:
            return None

    if not isinstance(value, dict):
        return None

    return key, value


# -----------------------------
# APPLY ENGINE
# -----------------------------
class LeafUnderlayApplier:

    def __init__(self):
        self.spine_leaf_client = SpineLeafClient(
            host=SPINE_LEAFSERVICE_HOST,
            port=SPINE_LEAFSERVICE_PORT,
        )

    def run(self):
        context_client = ContextClient(
            host=CONTEXTSERVICE_HOST,
            port=CONTEXTSERVICE_PORT,
        )

        device_client = DeviceClient(
            host=DEVICESERVICE_HOST,
            port=DEVICESERVICE_PORT,
        )

        try:
            for device_uuid in LEAF_DEVICE_UUIDS:
                self.process_device(context_client, device_client, device_uuid)

        finally:
            device_client.close()
            context_client.close()

    def process_device(self, context_client, device_client, device_uuid):

        LOGGER.info("Processing LEAF device: %s", device_uuid)

        request = DeployDeviceConfigRequest(
            fabric_id=FABRIC_ID,
            device_uuid=device_uuid,
        )

        response = self.spine_leaf_client.DeployDeviceConfig(request)

        payload = json.loads(getattr(response, "message", "") or "{}")

        raw_rules = payload.get("driver_config_rules", [])

        set_rules = []
        delete_rules = []

        deconfigure = is_deconfigure_enabled()
        apply = is_apply_enabled() or deconfigure

        for entry in raw_rules:
            parsed = parse_rule(entry)
            if not parsed:
                continue

            key, value = parsed

            set_rules.append(ConfigRule(**json_config_rule_set(key, value)))

            if deconfigure:
                delete_rules.append(ConfigRule(**json_config_rule_delete(key, value)))

        LOGGER.info(
            "Device %s -> rules generated: set=%d delete=%d",
            device_uuid,
            len(set_rules),
            len(delete_rules),
        )

        device = get_device(
            context_client,
            device_uuid,
            rw_copy=True,
            include_endpoints=False,
            include_config_rules=False,
            include_components=False,
        )

        if device is None:
            LOGGER.error("Device not found: %s", device_uuid)
            return

        working_device = copy.deepcopy(device)

        # IMPORTANT: prevent duplication on rerun (clear protobuf repeated field)
        del working_device.device_config.config_rules[:]

        if not apply:
            LOGGER.warning("DRY RUN - no changes applied for %s", device_uuid)
            return

        if deconfigure:
            LOGGER.info("DECONFIGURING LEAF %s", device_uuid)

            working_device.device_config.config_rules.extend(delete_rules)
            result = device_client.ConfigureDevice(working_device)

            LOGGER.info("Deconfig result %s => %s", device_uuid, result)

            self.spine_leaf_client.UnsetDeviceRole(
                UnsetDeviceRoleRequest(
                    fabric_id=FABRIC_ID,
                    device_uuid=device_uuid,
                )
            )

        else:
            LOGGER.info("APPLYING LEAF UNDERLAY %s", device_uuid)

            working_device.device_config.config_rules.extend(set_rules)
            result = device_client.ConfigureDevice(working_device)

            LOGGER.info("Apply result %s => %s", device_uuid, result)


# -----------------------------
# ENTRYPOINT
# -----------------------------
if __name__ == "__main__":
    LeafUnderlayApplier().run()
 No newline at end of file
+0 −324
Original line number Diff line number Diff line
# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import os
import copy

from common.proto.context_pb2 import ConfigRule
from common.proto.spineleaf_pb2 import DeployDeviceConfigRequest, UnsetDeviceRoleRequest
from common.tools.context_queries.Device import get_device
from common.tools.object_factory.ConfigRule import (
    json_config_rule_set,
    json_config_rule_delete,
)
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from spine_leaf.client.SpineLeafClient import SpineLeafClient

logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger("spineleaf_underlay_multi_test")

# -----------------------------
# ENV
# -----------------------------
CONTEXTSERVICE_HOST = os.getenv("CONTEXTSERVICE_SERVICE_HOST", "10.152.183.46")
CONTEXTSERVICE_PORT = int(os.getenv("CONTEXTSERVICE_SERVICE_PORT_GRPC", "1010"))

DEVICESERVICE_HOST = os.getenv("DEVICESERVICE_SERVICE_HOST", "10.152.183.102")
DEVICESERVICE_PORT = int(os.getenv("DEVICESERVICE_SERVICE_PORT_GRPC", "2020"))

SPINE_LEAFSERVICE_HOST = os.getenv("SPINE_LEAFSERVICE_SERVICE_HOST", "10.152.183.147")
SPINE_LEAFSERVICE_PORT = int(os.getenv("SPINE_LEAFSERVICE_SERVICE_PORT_GRPC", "10095"))

FABRIC_ID = os.getenv(
    "FABRIC_ID",
    "354a08b7-3730-5dd6-9472-47554115818b"
)

DEFAULT_DEVICE_UUIDS = [
    "e162b163-6969-512a-8ee9-cce9f0687c64",
    "1f3a8cbe-5826-585b-a207-4826d4ae9a61",
    "81b97fc5-6c85-5d0d-86e2-e00ff04a4740",
]


# -----------------------------
# HELPERS
# -----------------------------
def get_device_uuids():
    raw = os.getenv("DEVICE_UUIDS", "").strip()

    if not raw:
        return DEFAULT_DEVICE_UUIDS

    return [
        device_uuid.strip()
        for device_uuid in raw.split(",")
        if device_uuid.strip()
    ]


def is_deconfigure_enabled() -> bool:
    return os.getenv("DECONFIGURE", "false").lower() in (
        "1",
        "true",
        "yes",
    )


def is_apply_enabled() -> bool:
    return os.getenv("APPLY_CONFIG", "false").lower() in (
        "1",
        "true",
        "yes",
    )


def parse_spineleaf_rule(entry):
    if not isinstance(entry, (list, tuple)) or len(entry) != 2:
        return None

    resource_key, resource_value = entry

    if isinstance(resource_value, str):
        try:
            resource_value = json.loads(resource_value)
        except Exception:
            LOGGER.warning(
                "Invalid JSON value for %s: %s",
                resource_key,
                resource_value,
            )
            return None

    if not isinstance(resource_value, dict):
        LOGGER.warning(
            "Skipping non-dict value for %s: %s",
            resource_key,
            resource_value,
        )
        return None

    return resource_key, resource_value


# -----------------------------
# MAIN
# -----------------------------
class ApplySpineLeafConfig:

    def __init__(self) -> None:
        self.spine_leaf_client = SpineLeafClient(
            host=SPINE_LEAFSERVICE_HOST,
            port=SPINE_LEAFSERVICE_PORT,
        )

    def process_device(
        self,
        device_uuid,
        context_client,
        device_client,
    ):
        LOGGER.info("")
        LOGGER.info("=== DEVICE %s ===", device_uuid)

        request = DeployDeviceConfigRequest(
            fabric_id=FABRIC_ID,
            device_uuid=device_uuid,
        )

        response = self.spine_leaf_client.DeployDeviceConfig(request)

        LOGGER.info(
            "Raw SpineLeaf response received for %s",
            device_uuid,
        )

        payload = json.loads(
            getattr(response, "message", "") or "{}"
        )

        LOGGER.info(
            "Parsed payload keys for %s: %s",
            device_uuid,
            list(payload.keys()),
        )

        deconfigure = is_deconfigure_enabled()

        set_rules = []
        delete_rules = []

        for entry in payload.get("driver_config_rules", []):

            parsed = parse_spineleaf_rule(entry)
            if not parsed:
                continue

            resource_key, resource_value = parsed

            set_rule = json_config_rule_set(
                resource_key,
                resource_value,
            )
            set_rules.append(ConfigRule(**set_rule))

            if deconfigure:
                delete_rule = json_config_rule_delete(
                    resource_key,
                    resource_value,
                )
                delete_rules.append(ConfigRule(**delete_rule))

            LOGGER.info(
                "RULE %s => %s",
                resource_key,
                "DELETE" if deconfigure else "SET",
            )

        LOGGER.info(
            "Generated rules: set=%d delete=%d",
            len(set_rules),
            len(delete_rules),
        )

        device = get_device(
            context_client,
            device_uuid,
            rw_copy=True,
            include_endpoints=False,
            include_config_rules=False,
            include_components=False,
        )

        if device is None:
            LOGGER.error(
                "Device not found: %s",
                device_uuid,
            )
            return

        working_device = copy.deepcopy(device)

        should_apply = (
            is_apply_enabled()
            or deconfigure
        )

        if not should_apply:
            LOGGER.warning(
                "DRY RUN MODE - no changes applied"
            )
            return

        if deconfigure:

            LOGGER.info(
                "Applying DECONFIGURATION to %s",
                device_uuid,
            )

            working_device.device_config.config_rules.extend(
                delete_rules
            )

            result = device_client.ConfigureDevice(
                working_device
            )

            LOGGER.info(
                "DELETE result %s => %s",
                device_uuid,
                result,
            )

            unset_request = UnsetDeviceRoleRequest(
                fabric_id=FABRIC_ID,
                device_uuid=device_uuid,
            )

            unset_result = (
                self.spine_leaf_client.UnsetDeviceRole(
                    unset_request
                )
            )

            LOGGER.info(
                "UnsetDeviceRole %s => %s",
                device_uuid,
                unset_result,
            )

        else:

            LOGGER.info(
                "Applying CONFIGURATION to %s",
                device_uuid,
            )

            working_device.device_config.config_rules.extend(
                set_rules
            )

            result = device_client.ConfigureDevice(
                working_device
            )

            LOGGER.info(
                "SET result %s => %s",
                device_uuid,
                result,
            )

    def run(self):

        context_client = ContextClient(
            host=CONTEXTSERVICE_HOST,
            port=CONTEXTSERVICE_PORT,
        )

        device_client = DeviceClient(
            host=DEVICESERVICE_HOST,
            port=DEVICESERVICE_PORT,
        )

        try:

            for device_uuid in get_device_uuids():

                try:
                    self.process_device(
                        device_uuid,
                        context_client,
                        device_client,
                    )

                except Exception:
                    LOGGER.exception(
                        "Failed processing device %s",
                        device_uuid,
                    )

        finally:
            device_client.close()
            context_client.close()


# -----------------------------
# ENTRY
# -----------------------------
if __name__ == "__main__":
    ApplySpineLeafConfig().run()
 No newline at end of file
+0 −203

File deleted.

Preview size limit exceeded, changes collapsed.