Commit 33eb1071 authored by Pablo Armingol's avatar Pablo Armingol
Browse files

Refactor IPoWDM and E2E service handling: update data structures, add L3VPN...

Refactor IPoWDM and E2E service handling: update data structures, add L3VPN service generation, and implement frequency patching
parent edcd5ba5
Loading
Loading
Loading
Loading
+6 −18
Original line number Diff line number Diff line
@@ -10,33 +10,21 @@ message RuleEndpoint {

message DigitalSubCarrierId {
    int32 sub_carrier_id = 1;
    bool active = 2;
    string active = 2;
}

message DigitalSubCarriersGroup {
    int32 digital_sub_carriers_group_id = 1;
    int32 number_of_digital_subcarriers = 2;
    int32 digital_sub_carrier_group_size = 3;
    repeated DigitalSubCarrierId digital_sub_carrier_id = 4;
}

message OpticalChannelConfig {
    float frequency = 1;
    float target_output_power = 2;
    int32 operational_mode = 3;
    string line_port = 4;
    int32 total_number_of_digital_sub_carriers = 5;
    int32 digital_sub_carrier_spacing = 6;
    repeated DigitalSubCarriersGroup digital_sub_carriers_group = 7;
}

message OpticalChannel {
    OpticalChannelConfig config = 1;
}

message Component {
    string name = 1;
    OpticalChannel optical_channel = 2;
    float frequency = 2;
    float target_output_power = 3;
    int32 operational_mode = 4;
    repeated DigitalSubCarriersGroup digital_sub_carriers_group = 5;
    string operation = 6;
}

message Transceiver {
+109 −46
Original line number Diff line number Diff line
@@ -11,15 +11,82 @@
# 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.
from types import SimpleNamespace
import os
import json
import logging

import requests

LOGGER = logging.getLogger(__name__)

HEADERS = {
    "Accept": "application/yang-data+json",
    "Content-Type": "application/yang-data+json"
}

site_template = {
    "site-id": "",
    "devices": {
        "device": [
            {
                "device-id": "",
                "location": ""
            }
        ]
    },
    "site-network-accesses": {
        "site-network-access": [
            {
                "site-network-access-id": "",
                "device-reference": "",
                "ip-connection": {
                    "ipv4": {
                        "address-allocation-type": "ietf-l3vpn-svc:static-address",
                        "addresses": {
                            "provider-address": "",
                            "customer-address": "",
                            "prefix-length": ""
                        }
                    }
                },
                "vpn-attachment": {
                    "vpn-id": "vpn-p2mp"
                },
                "site-network-access-type": "ietf-l3vpn-svc:multipoint"
            }
        ]
    }
}

def generate_p2mp_l3vpn_service(sites_data):
    sites = []
    for idx, site in enumerate(sites_data):
        site_entry = json.loads(json.dumps(site_template))
        site_entry["site-id"] = site["uuid"]
        site_entry["devices"]["device"][0]["device-id"] = site["ip_address"]
        site_entry["devices"]["device"][0]["location"] = site.get("location", f"site-{idx+1}")
        site_entry["site-network-accesses"]["site-network-access"][0]["site-network-access-id"] = f"access-{site['uuid']}"
        site_entry["site-network-accesses"]["site-network-access"][0]["device-reference"] = site["ip_address"]
        site_entry["site-network-accesses"]["site-network-access"][0]["ip-connection"]["ipv4"]["addresses"]["provider-address"] = site["ip_address"]
        site_entry["site-network-accesses"]["site-network-access"][0]["ip-connection"]["ipv4"]["addresses"]["customer-address"] = site["ip_address"]
        site_entry["site-network-accesses"]["site-network-access"][0]["ip-connection"]["ipv4"]["addresses"]["prefix-length"] = site["ip_mask"]
        sites.append(site_entry)

    service = {
        "ietf-l3vpn-svc:l3vpn-svc": {
            "sites": {
                "site": sites
            },
            "vpn-services": {
                "vpn-service": [
                    {
                        "vpn-id": "vpn-p2mp",
                        "vpn-service-topology": "ietf-l3vpn-svc:multipoint"
                    }
                ]
            }
        }
    }
    return json.dumps(service, indent=2)

def create_request(resource_value):
    """ Create and send HTTP request based on a JSON template and provided resource value.
        The JSON template is expected to be in the same directory as this script, named 'ipowdm.json'.
@@ -41,52 +108,48 @@ def create_request(resource_value):

    LOGGER.info("Creating request for resource_value: %s", resource_value)

    data = resource_value[1]['rule_set']['transceiver']
    node_src = resource_value[1]['rule_set']['src'][0]
    src = [{
        'uuid': node_src["uuid"],
        'ip_address': node_src["ip_address"],
        'ip_mask': node_src["ip_mask"],
        'vlan_id': node_src["vlan_id"]
    }]
    dst_list = resource_value[1]['rule_set']['dst']
    dst = []
    for node in dst_list:
        dst.append({
            'uuid': node["uuid"],
            'ip_address': node["ip_address"],
            'ip_mask': node["ip_mask"],
            'vlan_id': node["vlan_id"]
        })
    
    LOGGER.info("Sending POST DSTINATION request with payload: %s", json.dumps(data, indent=2))
    sites_input = src + dst
    
    response = FakeResponse()
    components = resource_value[1]['rule_set']['transceiver']['components']
    for i, device in enumerate(components):
        name = sites_input[i]['uuid']

    return response
        if name == "T2.1":device["frequency"]= 195000000
        if name == "T1.1":device["frequency"]= 195006250
        if name == "T1.2":device["frequency"]= 195018750
        if name == "T1.3":device["frequency"]= 195031250

class FakeResponse:
    """_Fake response object for testing purposes."""
    def __init__(self):
        self.ok = True
        self.status_code = 200
        self.text = '{"message": "OK"}'

    def json(self):
        """Return a sample JSON response."""
        return {"message": "OK"}
def tfs_post(request):
        """
        Send a POST request to the TeraFlow Service Orchestrator.
        LOGGER.debug(f"NODE DATA: \n{name}: {json.dumps(device, indent=2)}")
        response = patch_optical_channel_frequency(device, name)
        LOGGER.debug(f"RESPONSE :\n {response}")

        Args:
            ip (str): IP address of the TeraFlow Service Orchestrator.
            request (dict): The request payload to be sent.
    p2mp_json = generate_p2mp_l3vpn_service(sites_input)
    LOGGER.debug(f"Generated L3VPN P2MP service JSON:\n{p2mp_json}")
    return response

        Returns:
            dict: The response from the TeraFlow Service Orchestrator.
        """
        user="admin"
        password="admin"
        token=""
        session = requests.Session()
        session.auth = (user, password)
        url=f'http://10.95.86.62/webui'
        response=session.get(url=url)
        for item in response.iter_lines():
            if"csrf_token" in str(item):
                string=str(item).split('<input id="csrf_token" name="csrf_token" type="hidden" value=')[1]
                token=string.split(">")[0].strip('"')
        logging.debug("csrf token %s",token)

        files = {'descriptors': ("data.json", json.dumps(request).encode(
                                                                "utf-8"), "application/json")}
        token={'csrf_token':token}
        response = session.post(url,files=files,data=token,timeout=60)
        logging.debug("Http response: %s",response.text)
def patch_optical_channel_frequency(data, DEVICE_ID):
    encoded_path = f"http://192.168.202.254:80/restconf/data/device={DEVICE_ID}/openconfig-platform:components/component=channel-1/optical-channel/config"

    patch_data = data
    response = requests.patch(f"{encoded_path}",
                            json=patch_data,
                            headers=HEADERS)
    assert response.status_code == 200
    return response
+51 −19
Original line number Diff line number Diff line
@@ -11,35 +11,54 @@
# 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 ast
import json
import logging

import requests
from flask_restful import Resource, request
from flask_restful import Resource
from common.Constants import DEFAULT_CONTEXT_NAME
from service.client.ServiceClient import ServiceClient
from .Tools import grpc_service_id

LOGGER = logging.getLogger(__name__)


HEADERS = {
    "Accept": "application/yang-data+json",
    "Content-Type": "application/yang-data+json"
}

class E2EInfoDelete(Resource):
    def __init__(self):
        super().__init__()
        self.service_client = ServiceClient()

    def delete(self, allocationId: str):
        LOGGER.info("Received DELETE request for allocationId: %s", allocationId)

        service_type = None
        if 'ipowdm' in allocationId: service_type = 'IPoWDM'
        LOGGER.info("Service type identified as: %s", service_type)

        if service_type == 'IPoWDM':
            LOGGER.info("Deleting IPoWDM service with allocationId: %s", allocationId)
            data = allocationId.split(':')[-1]
            allocationId = allocationId.split('=')[1].split(':')[0]
            url = f'http://10.95.86.62/restconf/E2E/v1/service/ipowdm={allocationId}'
            LOGGER.info("DELETE URL: %s", url)
            # response = requests.delete(url, timeout=10)
        service_id = ""
        if 'ipowdm' in allocationId:
            service_id    = allocationId.split('=')[1].split(':')[0].split('[[')[0]
            nodes_str      = allocationId.split('[[')[1].split(']]')[0]
            nodes_list_str = nodes_str + ']'
            nodes      = ast.literal_eval(nodes_list_str)
            data_str   = allocationId.split(':', 1)[1]
            data       = json.loads(data_str)
            components = data.get("components", [])

            for i, device in enumerate(components):
                if i >= len(nodes):
                    LOGGER.warning(f"Index {i} exceeds nodes list length {len(nodes)}")
                    break
                name = nodes[i]
                if name == "T2.1":device["frequency"]= 195000000
                if name == "T1.1":device["frequency"]= 195006250
                if name == "T1.2":device["frequency"]= 195018750
                if name == "T1.3":device["frequency"]= 195031250
               
                LOGGER.debug(f"NODE DATA: \n{name}:{device}")
                response = test_patch_optical_channel_frequency(device, name)
                LOGGER.debug(f"RESPONSE :\n {response}")

        else:
            LOGGER.error("Unknown service type for allocationId: %s", allocationId)
            return {
@@ -47,13 +66,26 @@ class E2EInfoDelete(Resource):
                'message': 'Unknown service type',
            }, 400


        LOGGER.info("Mock DELETE request sent to URL: %s", url)
        LOGGER.info("Allocation ID: %s", allocationId)
        service_id = grpc_service_id(DEFAULT_CONTEXT_NAME, allocationId)
        service_id = grpc_service_id(DEFAULT_CONTEXT_NAME, service_id)
        self.service_client.DeleteService(service_id)

        return {
            'status': 'Service deleted',
            'allocationId': allocationId,
        }, 200

def test_patch_optical_channel_frequency(data, DEVICE_ID):
    """Test PATCH to update optical channel frequency."""
    # Use simple path with / and encode manually for component name
    encoded_path = f"http://192.168.202.254:80/restconf/data/device={DEVICE_ID}/openconfig-platform:components/component=channel-1/optical-channel/config"

    # Update frequency
    patch_data = data

    response = requests.patch(f"{encoded_path}",
                            json=patch_data,
                            headers=HEADERS)
    assert response.status_code == 200

    # Validate restoration
    return response
 No newline at end of file