Commit fec29426 authored by Pablo Armingol's avatar Pablo Armingol
Browse files

Merge branch...

Merge branch 'feat/327-tid-new-service-to-ipowdm-controller-to-manage-transceivers-configuration-on-external-agent' of https://labs.etsi.org/rep/tfs/controller into feat/327-tid-new-service-to-ipowdm-controller-to-manage-transceivers-configuration-on-external-agent
parents e2d4490f d0e52ec9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@
                    "uuid": "644c4aa6-c2e2-4db0-9d6e-869522c4141c"
                }
            },
            "service_type": 12,
            "service_type": 13,
            "service_status": {
                "service_status": 1
            },
+49 −274
Original line number Diff line number Diff line
@@ -11,230 +11,15 @@
# 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 os

import requests
from concurrent.futures import ThreadPoolExecutor

LOGGER = logging.getLogger(__name__)

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

executor = ThreadPoolExecutor()

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_l3vpn_template_pair(src, dst, vpn_id):

    return {
        "ietf-l3vpn-svc:l3vpn-svc": {
            "vpn-services": {
                "vpn-service": [{"vpn-id": vpn_id}]
            },
            "sites": {
                "site": [
                    {
                        "site-id": src["uuid"],
                        "management": {"type": "ietf-l3vpn-svc:provider-managed"},
                        "locations": {"location": [{"location-id": f"location-{src['uuid']}"}]},
                        "devices": {"device": [{
                            "device-id": "10.0.30.1",
                            "location": f"location-{src['uuid']}"
                        }]},
                        "routing-protocols": {"routing-protocol": [{
                            "type": "ietf-l3vpn-svc:static",
                            "static": {
                                "cascaded-lan-prefixes": {
                                    "ipv4-lan-prefixes": [
                                        {
                                        "lan": "128.32.10.1/24",
                                        "lan-tag": f"vlan{src['vlan_id']}",
                                        "next-hop": "10.0.30.10"
                                        }
                                    ]
                                }
                            }
                        }]},
                        "site-network-accesses": {
                            "site-network-access": [{
                                "site-network-access-id": f"{src['vlan_id']}",
                                "site-network-access-type": "ietf-l3vpn-svc:multipoint",
                                "device-reference": "10.0.30.1",
                                "vpn-attachment": {
                                    "vpn-id": vpn_id, "site-role": "ietf-l3vpn-svc:spoke-role"
                                },
                                "ip-connection": {
                                    "ipv4": {
                                        "address-allocation-type": "ietf-l3vpn-svc:static-address",
                                        "addresses": {
                                            "provider-address": "10.0.30.254",
                                            "customer-address": "10.0.30.10",
                                            "prefix-length": 24
                                        }
                                    }
                                },
                                "routing-protocols": {"routing-protocol": [{
                                    "type": "ietf-l3vpn-svc:static",
                                    "static": {
                                        "cascaded-lan-prefixes": {
                                            "ipv4-lan-prefixes": [
                                                {
                                                "lan": "172.1.101.1/24",
                                                "lan-tag": "vlan100",
                                                "next-hop": "10.0.30.254"
                                                }
                                            ]
                                        }
                                    }
                                }]},
                                "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": dst["uuid"],
                        "management": {"type": "ietf-l3vpn-svc:provider-managed"},
                        "locations": {"location": [{"location-id": f"location-{dst['uuid']}"}]},
                        "devices": {"device": [{
                            "device-id": "10.0.20.1",
                            "location": f"location-{dst['uuid']}"
                        }]},
                        "routing-protocols": {"routing-protocol": [{
                            "type": "ietf-l3vpn-svc:static",
                            "static": {
                                "cascaded-lan-prefixes": {
                                    "ipv4-lan-prefixes": [
                                        {
                                        "lan": "172.1.101.1/24",
                                        "lan-tag": "vlan200",
                                        "next-hop": "172.10.33.2"
                                        }
                                    ]
                                }
                            }
                        }]},
                        "site-network-accesses": {
                            "site-network-access": [{
                                "site-network-access-id": f"{dst['vlan_id']}",
                                "site-network-access-type": "ietf-l3vpn-svc:multipoint",
                                "device-reference": "10.0.20.1",
                                "vpn-attachment": {
                                    "vpn-id": vpn_id, "site-role": "ietf-l3vpn-svc:hub-role"
                                },
                                "ip-connection": {
                                    "ipv4": {
                                        "address-allocation-type": "ietf-l3vpn-svc:static-address",
                                        "addresses": {
                                            "provider-address": "172.10.33.254",
                                            "customer-address": "172.10.33.2",
                                            "prefix-length": 24
                                        }
                                    }
                                },
                                "routing-protocols": {"routing-protocol": [{
                                    "type": "ietf-l3vpn-svc:static",
                                    "static": {
                                        "cascaded-lan-prefixes": {
                                            "ipv4-lan-prefixes": [
                                                {
                                                "lan": "128.32.10.1/24",
                                                "lan-tag": "vlan200",
                                                "next-hop": "172.10.33.254"
                                                }
                                            ]
                                        }
                                    }
                                }]},
                                "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
                                                }
                                            }
                                            ]
                                        }
                                        }
                                    }
                                }
                            }]
                        }
                    }
                ]
            }
        }
    }

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'.
@@ -261,61 +46,51 @@ def create_request(resource_value):
    with open(json_path, 'r', encoding='utf-8') as f:
        template = json.load(f)

    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']
    dsts = []
    for node in dst_list:
        dsts.append({
            'uuid': node["uuid"],
            'ip_address': node["ip_address"],
            'ip_mask': node["ip_mask"],
            'vlan_id': node["vlan_id"]
        })

    sites_input = src + dsts

    components = resource_value[1]['rule_set']['transceiver']['components']
    for i, device in enumerate(components):
        name = sites_input[i]['uuid']
    LOGGER.info("Sending POST DSTINATION request with payload: %s", json.dumps(template, indent=2))

        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
    response = FakeResponse()
    # tfs_post(template)
    return response

        LOGGER.debug(f"NODE TO CONFIGURE: \n{name}: {json.dumps(device, indent=2)}")
        response = patch_optical_channel_frequency(device, name)
        LOGGER.debug(f"RESPONSE :\n {response}")
    templates = []
    for dst in dsts:
        vpn = "L3VPN_"+src[0]['uuid']+"_"+dst['uuid']
        templates.append(generate_l3vpn_template_pair(src[0], dst,vpn))
class FakeResponse:
    """_Fake response object for testing purposes."""
    def __init__(self):
        self.ok = True
        self.status_code = 200
        self.text = '{"message": "OK"}'

    url = "http://192.168.202.254:80/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services"
    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/json'
    }
    def json(self):
        """Return a sample JSON response."""
        return {"message": "OK"}

    for template in templates:
        LOGGER.info("Generated L3VPN P2MP service JSON:\n%s", json.dumps(template, indent=2))
def tfs_post(request):
        """
        Send a POST request to the TeraFlow Service Orchestrator.

        response = requests.post(url = url, headers= headers, json=template)
        LOGGER.debug(response)
    return None
        Args:
            ip (str): IP address of the TeraFlow Service Orchestrator.
            request (dict): The request payload to be sent.

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"
        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)

    patch_data = data
    response = requests.patch(f"{encoded_path}",
                            json=patch_data,
                            headers=HEADERS)
    assert response.status_code == 200
        return response
+32 −37
Original line number Diff line number Diff line
@@ -34,55 +34,50 @@ class IPoWDMService(Resource):
        request_data = request.get_json()
        LOGGER.info("IPoWDM request data: %s", json.dumps(request_data, indent=2))

        # Identify Service Type and Device ID
        device_id = request_data.get('device_id', request_data.get('device', 'TFS-PACKET'))
        service_type = ServiceTypeEnum.SERVICETYPE_L3NM

        if 'ietf-l3vpn-svc:l3vpn-svc' in request_data:
            service_type = ServiceTypeEnum.SERVICETYPE_L3NM
        elif 'config' in request_data:
            service_type = ServiceTypeEnum.SERVICETYPE_IPOWDM

        # Validate required fields (Legacy support or new checks?)
        if service_type == ServiceTypeEnum.SERVICETYPE_L3NM and 'ietf-l3vpn-svc:l3vpn-svc' not in request_data:
        # Validate required fields
        if 'src' not in request_data or 'dst' not in request_data:
                 pass
            return {'status': 'error', 'message': 'Missing required fields: src and dst'}, 400

        # Extract key information
        src_endpoints = request_data.get('src', [])
        dst_endpoints = request_data.get('dst', [])
        bandwidth = request_data.get('bw', 100)
        device_id = request_data.get('device_id', 'TFS-PACKET')

        LOGGER.info(f"Service UUID: {serviceId}")
        LOGGER.info(f"Device ID: {device_id}")
        LOGGER.info(f"Service Type: {service_type}")

        # Create TFS Service
        # Create TFS Service for visibility in WebUI
        try:
            service = Service()
            service.service_id.service_uuid.uuid = serviceId
            service.service_id.context_id.context_uuid.uuid = "admin"
            service.service_type = service_type
            service.service_type = ServiceTypeEnum.SERVICETYPE_IPOWDM
            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
            service.name = f"IPoWDM-{serviceId}"

            # Create service in TFS
            service_response = self.service_client.CreateService(service)
            LOGGER.info("Created TFS IPoWDM service: %s", service_response)
        except grpc.RpcError as e:
            if e.code() != grpc.StatusCode.ALREADY_EXISTS:
                LOGGER.error("Failed to create TFS IPoWDM service: %s", str(e), exc_info=True)
                return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500
            LOGGER.warning("TFS IPoWDM service already exists: %s", serviceId)

        except Exception as e:
            LOGGER.error("Failed to create TFS IPoWDM service: %s", str(e), exc_info=True)
            return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500

        # Configure Device
        if device_id:
        # Send to device for processing
        try:
            device = Device()
            device.device_id.device_uuid.uuid = device_id

            # Create config rule with IPoWDM data
            config_rule = ConfigRule()
            config_rule.action = ConfigActionEnum.CONFIGACTION_SET
                config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}/{device_id}'
            config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}'

            # Store the complete IPoWDM configuration
            config_rule.custom.resource_value = json.dumps(request_data)

            # Add rule and configure device
            device.device_config.config_rules.append(config_rule)
            self.device_client.ConfigureDevice(device)
            LOGGER.info("Configured device %s with IPoWDM service %s", device_id, serviceId)
@@ -90,8 +85,6 @@ class IPoWDMService(Resource):
        except Exception as e:
            LOGGER.error("Failed to configure device: %s", str(e))
            return {'status': 'error', 'message': f'Failed to configure device: {str(e)}'}, 500
        else:
             LOGGER.warning("No device_id provided, IPoWDM service not configured on device.")

        return {
            'status': 'success',
@@ -114,6 +107,7 @@ class IPoWDMService(Resource):
            service_id.service_uuid.uuid = serviceId
            service_id.context_id.context_uuid.uuid = "admin"

            # Delete service from TFS
            self.service_client.DeleteService(service_id)
            LOGGER.info("Deleted TFS IPoWDM service: %s", serviceId)

@@ -127,6 +121,7 @@ class IPoWDMService(Resource):
                device = Device()
                device.device_id.device_uuid.uuid = device_id

                # Create DELETE config rule
                config_rule = ConfigRule()
                config_rule.action = ConfigActionEnum.CONFIGACTION_DELETE
                config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}/{device_id}'
+146 −0
Original line number Diff line number Diff line
#!/bin/bash

# Test IPoWDM service creation and deletion via NBI
# Tests the endpoint /restconf/ipowdm/v1/service/{uuid}

set -e

NBI_URL="${NBI_URL:-http://10.95.86.58}"

echo "=========================================="
echo "IPoWDM NBI Test - Create and Delete"
echo "=========================================="
echo ""

# Test counters
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0

# Function to run a test
run_test() {
    local test_name="$1"
    local method="$2"
    local endpoint="$3"
    local data="$4"
    local expected_status="$5"

    TESTS_RUN=$((TESTS_RUN + 1))
    echo ">>> Test $TESTS_RUN: $test_name"
    echo ""

    if [ -n "$data" ]; then
        response=$(curl -v -X "$method" \
            "$NBI_URL$endpoint" \
            -H "Content-Type: application/json" \
            -d "$data" \
            2>&1)
    else
        response=$(curl -v -X "$method" \
            "$NBI_URL$endpoint" \
            -H "Content-Type: application/json" \
            2>&1)
    fi

    echo "$response"
    echo ""

    # Check if expected status is in response
    if echo "$response" | grep -q "HTTP.*$expected_status"; then
        echo "✓ PASSED: Got expected status $expected_status"
        TESTS_PASSED=$((TESTS_PASSED + 1))
    else
        echo "✗ FAILED: Expected status $expected_status"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi

    echo ""
    echo "Waiting 2 seconds..."
    sleep 2
    echo ""
}

# Test 1: Create IPoWDM Service
echo "==========================================
TEST 1: CREATE IPOWDM SERVICE
==========================================
"

IPOWDM_CREATE_DATA='{
  "src": [{
    "uuid": "Phoenix-1",
    "ip_address": "10.10.1.1",
    "ip_mask": "/24",
    "vlan_id": 100
  }],
  "dst": [{
    "uuid": "Phoenix-2",
    "ip_address": "10.10.2.1",
    "ip_mask": "/24",
    "vlan_id": 100
  }],
  "bw": 100,
  "uuid": "test-ipowdm-001",
  "transceiver": {
    "components": [
      {
        "name": "Optics0/0/0/1",
        "frequency": 194700.0,
        "target_output_power": -10.0,
        "operational_mode": 0,
        "digital_sub_carriers_group": [],
        "operation": "activate"
      },
      {
        "name": "Optics0/0/0/2",
        "frequency": 194700.0,
        "target_output_power": -10.0,
        "operational_mode": 0,
        "digital_sub_carriers_group": [],
        "operation": "activate"
      }
    ]
  },
  "device_id": "TFS-PACKET"
}'

run_test \
    "Create IPoWDM Service" \
    "POST" \
    "/restconf/ipowdm/v1/service/test-ipowdm-001" \
    "$IPOWDM_CREATE_DATA" \
    "201"

# Test 2: Delete IPoWDM Service
echo "==========================================
TEST 2: DELETE IPOWDM SERVICE
==========================================
"

IPOWDM_DELETE_DATA='{
  "device_id": "TFS-PACKET"
}'

run_test \
    "Delete IPoWDM Service" \
    "DELETE" \
    "/restconf/ipowdm/v1/service/test-ipowdm-001" \
    "$IPOWDM_DELETE_DATA" \
    "200"

# Test Summary
echo "=========================================="
echo "TEST SUMMARY"
echo "=========================================="
echo "Total Tests Run:    $TESTS_RUN"
echo "Tests Passed:       $TESTS_PASSED"
echo "Tests Failed:       $TESTS_FAILED"
echo "=========================================="

if [ $TESTS_FAILED -eq 0 ]; then
    echo "✓ ALL TESTS PASSED"
    exit 0
else
    echo "✗ SOME TESTS FAILED"
    exit 1
fi