Loading proto/ipowdm.proto +6 −18 Original line number Diff line number Diff line Loading @@ -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 { Loading src/device/service/drivers/ietf_l3vpn/templates/tools.py +109 −46 Original line number Diff line number Diff line Loading @@ -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'. Loading @@ -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 src/nbi/service/e2e_services/Resources.py +51 −19 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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 Loading
proto/ipowdm.proto +6 −18 Original line number Diff line number Diff line Loading @@ -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 { Loading
src/device/service/drivers/ietf_l3vpn/templates/tools.py +109 −46 Original line number Diff line number Diff line Loading @@ -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'. Loading @@ -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
src/nbi/service/e2e_services/Resources.py +51 −19 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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