From 59ba5961e07caa2028e9380ad0f6f7e2db6c4505 Mon Sep 17 00:00:00 2001 From: velazquez Date: Wed, 20 May 2026 09:40:24 +0200 Subject: [PATCH 1/2] Update code commentaries --- src/api/main.py | 8 +- src/database/sysrepo_store.py | 84 +++++++++---------- src/mapper/extract_sdp_info.py | 4 +- src/mapper/slo_viability.py | 2 +- src/realizer/ixia/helpers/NEII_V4.py | 10 +-- src/realizer/ixia/main.py | 4 +- .../service_types/builders/configure_slos.py | 8 +- .../builders/create_site_from_sdp.py | 2 +- src/realizer/restconf/service_types/l2vpn.py | 2 +- src/realizer/restconf/service_types/l3vpn.py | 2 +- src/tests/test_e2e.py | 16 ++-- src/tests/test_initialization.py | 4 +- src/tests/test_nbi_processor.py | 12 +-- src/webui/gui.py | 6 +- 14 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/api/main.py b/src/api/main.py index 0fe0ff3..b672f96 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -419,7 +419,7 @@ class Api: if not existing_data: return send_response(False, code=404, message="Network slice services not found") - # Si no está en modo DUMMY, procesar con TFS + # If not in DUMMY mode, process with TFS result = self.slice_service.nsc(intent) if not result: return send_response(False, code=500, message="Failed to process slice in TFS") @@ -474,7 +474,7 @@ class Api: if not result: return send_response(False, code=500, message="Slice not updated") - # Eliminar el ID del body si existe (ya está en el predicado) + # Remove the ID from the body if it exists (it's already in the predicate) template_data = template.copy() template_data.pop("id", None) @@ -544,7 +544,7 @@ class Api: if not result: return send_response(False, code=500, message="Slice not updated") - # Eliminar el ID del body (ya está en el predicado) + # Remove the ID from the body (it's already in the predicate) intent_data = intent.copy() intent_data.pop("id", None) @@ -587,7 +587,7 @@ class Api: if "id" in sdp and sdp["id"] != sdp_id: return send_response(False, code=400, message="SDP ID in body does not match URL") - # Eliminar el ID del body (ya está en el predicado) + # Remove the ID from the body (it's already in the predicate) sdp_data = sdp.copy() sdp_data.pop("id", None) diff --git a/src/database/sysrepo_store.py b/src/database/sysrepo_store.py index 22b08a6..15cb874 100644 --- a/src/database/sysrepo_store.py +++ b/src/database/sysrepo_store.py @@ -21,13 +21,13 @@ import logging def _get_connection(): """ - Crea una nueva conexión cada vez + Creates a new connection each time """ return sysrepo.SysrepoConnection() def create_data_store(intent: dict, xpath: str= ""): """ - CREATE: Crea nuevos datos en el datastore + CREATE: Creates new data in the datastore """ conn = _get_connection() sess = conn.start_session() @@ -48,7 +48,7 @@ def create_data_store(intent: dict, xpath: str= ""): def get_data_store(xpath: str = ""): """ - Obtiene todos los slices del datastore + Gets all slices from the datastore """ conn = _get_connection() sess = conn.start_session() @@ -69,23 +69,23 @@ def get_data_store(xpath: str = ""): def update_data_store(intent: dict, xpath: str = ""): """ - UPDATE: Modifica datos en el datastore - + UPDATE: Modifies data in the datastore + Args: - intent: Datos a modificar - xpath: Ruta al recurso - operation: - - "merge" (default): PATCH - actualiza campos específicos - - "replace": PUT - reemplaza completamente el recurso - - "create": POST - solo crea si no existe - + intent: Data to modify + xpath: Path to resource + operation: + - "merge" (default): PATCH - updates specific fields + - "replace": PUT - completely replaces the resource + - "create": POST - only creates if it doesn't exist + Returns: - bool: True si tuvo éxito + bool: True if successful """ conn = _get_connection() sess = conn.start_session() - # PUT: Eliminar y recrear (reemplazo completo) + # PUT: Delete and recreate (complete replacement) try: if not xpath: sess.delete_item("/ietf-network-slice-service:network-slice-services") @@ -107,25 +107,25 @@ def update_data_store(intent: dict, xpath: str = ""): def patch_data_store(intent: dict, xpath: str = ""): """ - UPDATE: Modifica datos en el datastore - + UPDATE: Modifies data in the datastore + Args: - intent: Datos a modificar - xpath: Ruta al recurso - operation: - - "merge" (default): PATCH - actualiza campos específicos - - "replace": PUT - reemplaza completamente el recurso - - "create": POST - solo crea si no existe - + intent: Data to modify + xpath: Path to resource + operation: + - "merge" (default): PATCH - updates specific fields + - "replace": PUT - completely replaces the resource + - "create": POST - only creates if it doesn't exist + Returns: - bool: True si tuvo éxito + bool: True if successful """ conn = _get_connection() sess = conn.start_session() - - # PUT: Eliminar y recrear (reemplazo completo) + + # PUT: Delete and recreate (complete replacement) try: - # PATCH: Merge con datos existentes + # PATCH: Merge with existing data _write_dict(sess, xpath, intent) sess.apply_changes() logging.debug(f"Merged data at {xpath}") @@ -141,7 +141,7 @@ def patch_data_store(intent: dict, xpath: str = ""): def delete_data_store(xpath: str = ""): """ - DELETE: Elimina datos del datastore + DELETE: Deletes data from the datastore """ conn = _get_connection() sess = conn.start_session() @@ -161,8 +161,8 @@ def delete_data_store(xpath: str = ""): def _write_dict(sess, base_xpath, data, parent_key=None): """ - Convierte dict → XPaths YANG - parent_key: clave que ya está en el xpath y debe ser excluida + Converts dict → YANG XPaths + parent_key: key already in xpath and should be excluded """ LIST_KEYS = { 'slo-sle-template': 'id', @@ -201,13 +201,13 @@ def _write_dict(sess, base_xpath, data, parent_key=None): # ----------------------------- if isinstance(data, dict): - # Saltar diccionarios vacíos + # Skip empty dictionaries if not data: logging.debug("Skipping empty dict") return - + for k, v in data.items(): - # IMPORTANTE: Saltar la clave si ya está en el predicado + # IMPORTANT: Skip the key if already in the predicate if k == parent_key: logging.debug(f"Skipping key field '{k}' (already in predicate)") continue @@ -219,7 +219,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): # ----------------------------- elif isinstance(data, list): - # Saltar listas vacías + # Skip empty lists if not data: logging.debug("Skipping empty list") return @@ -233,7 +233,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): if is_leaf_list: # ----------------------------- - # LEAF-LIST: Lista de valores simples + # LEAF-LIST: List of simple values # ----------------------------- logging.debug(f"Processing as LEAF-LIST: {list_name}") @@ -243,13 +243,13 @@ def _write_dict(sess, base_xpath, data, parent_key=None): continue logging.debug(f"Adding leaf-list value: {value}") - # En sysrepo, los leaf-lists se agregan con el mismo xpath - # pero múltiples valores + # In sysrepo, leaf-lists are added with the same xpath + # but multiple values sess.set_item(base_xpath, str(value)) else: # ----------------------------- - # LIST: Lista de containers + # LIST: List of containers # ----------------------------- logging.debug(f"Processing as LIST: {list_name}") key_field = LIST_KEYS.get(list_name) @@ -260,7 +260,7 @@ def _write_dict(sess, base_xpath, data, parent_key=None): item_xpath = f"{base_xpath}[{key_field}='{key_value}']" logging.debug(f"Using key '{key_field}={key_value}' for list '{list_name}'") - # Pasar el key_field para que sea excluido al procesar el item + # Pass the key_field to be excluded when processing the item _write_dict(sess, item_xpath, item, parent_key=key_field) else: logging.error(f"ERROR: No key '{key_field}' found in item for list '{list_name}'") @@ -292,18 +292,18 @@ def normalize_libyang_data(data): KeyedList = None if KeyedList and isinstance(data, KeyedList): - # KeyedList -> lista normal + # KeyedList -> normal list return [normalize_libyang_data(item) for item in data] elif isinstance(data, dict): - # Dict -> procesar valores recursivamente + # Dict -> process values recursively return { key: normalize_libyang_data(value) for key, value in data.items() } elif isinstance(data, (list, tuple)): - # Lista/tupla -> procesar elementos recursivamente + # List/tuple -> process elements recursively return [normalize_libyang_data(item) for item in data] else: diff --git a/src/mapper/extract_sdp_info.py b/src/mapper/extract_sdp_info.py index dafbcf5..1c77831 100644 --- a/src/mapper/extract_sdp_info.py +++ b/src/mapper/extract_sdp_info.py @@ -32,12 +32,12 @@ def extract_sdp_info(sdp_id, slice_service, connection_group_id, connectivity_co logging.debug(f"Looking for match criteria with target-connectivity-construct-id: {connectivity_construct_id}") selected_match_criteria = next((mc for mc in match_criteria_list if safe_get(mc, ["target-connectivity-construct-id"]) == connectivity_construct_id), None) - # Si no, buscar por connection group + # If not, search by connection group if not selected_match_criteria and connection_group_id: logging.debug(f"Looking for match criteria with target-connection-group-id: {connection_group_id}") selected_match_criteria = next((mc for mc in match_criteria_list if safe_get(mc, ["target-connection-group-id"]) == connection_group_id), None) - # Si no, usar el primero disponible + # If not, use the first available if not selected_match_criteria: logging.debug("No specific match criteria found for connectivity construct or connection group. Using the first available match criteria.") selected_match_criteria = match_criteria_list[0] diff --git a/src/mapper/slo_viability.py b/src/mapper/slo_viability.py index 3634a17..cc6ea62 100644 --- a/src/mapper/slo_viability.py +++ b/src/mapper/slo_viability.py @@ -61,4 +61,4 @@ def slo_viability(slice_slos, nrp_slos): # Calculate final viability score score = sum(flexibility_scores) / len(flexibility_scores) if flexibility_scores else 0 - return True, score # Si pasó todas las verificaciones, la NRP es viable \ No newline at end of file + return True, score # If it passed all verifications, the NRP is viable \ No newline at end of file diff --git a/src/realizer/ixia/helpers/NEII_V4.py b/src/realizer/ixia/helpers/NEII_V4.py index 4f9a249..16ddaeb 100644 --- a/src/realizer/ixia/helpers/NEII_V4.py +++ b/src/realizer/ixia/helpers/NEII_V4.py @@ -41,7 +41,7 @@ class NEII_controller: self.existentes(ip) return - ## FUNCIONES MENÚ PRINCIPAL ## + ## MAIN MENU FUNCTIONS ## def ver_info(self,ip): ''' @@ -173,9 +173,9 @@ class NEII_controller: drops = json_data.get("drops", None) desv_drop = json_data.get("desv_drop", None) - # --- Variables de configuración --- + # --- Configuration variables --- - # Configuración de IPv4 / IPv6 + # IPv4 / IPv6 configuration if src_node_ip and dst_node_ip: if isinstance(ipaddress.ip_address(src_node_ip), ipaddress.IPv4Address) and isinstance(ipaddress.ip_address(dst_node_ip), ipaddress.IPv4Address): configuraciones['ipv4'] = self.ipv4(src_node_ip, dst_node_ip, 5) @@ -214,12 +214,12 @@ class NEII_controller: dataProfile['profiles'].append(configuracion_perfil) logging.info(f"Configuración del perfil: {configuracion_perfil}") - # Enviar la configuración + # Send the configuration automatizacion.envio_peticion(ip, puerto, dataProfile) return automatizacion.obtener_informacion_puerto(ip, puerto) - ## FUNCIONES DE CONFIGURACIÓN DE PUERTO ## + ## PORT CONFIGURATION FUNCTIONS ## def delay(self,delay_perfil): ''' diff --git a/src/realizer/ixia/main.py b/src/realizer/ixia/main.py index 5bcfcce..ff3c890 100644 --- a/src/realizer/ixia/main.py +++ b/src/realizer/ixia/main.py @@ -44,7 +44,7 @@ def ixia(ietf_intent): latency = None tolerance = None - # Asignar valores según el tipo de métrica + # Assign values according to metric type for metric in metric_bounds: metric_type = metric.get("metric-type") bound = metric.get("bound") @@ -56,7 +56,7 @@ def ixia(ietf_intent): elif metric_type == "one-way-delay-variation-maximum": tolerance = bound - # Construcción del diccionario intent + # Construction of the intent dictionary intent = { "src_node_ip": ietf_intent.get("ietf-network-slice-service:network-slice-services", {}) .get("slice-service", [{}])[0] diff --git a/src/realizer/restconf/service_types/builders/configure_slos.py b/src/realizer/restconf/service_types/builders/configure_slos.py index 6a4f5d1..7721e51 100644 --- a/src/realizer/restconf/service_types/builders/configure_slos.py +++ b/src/realizer/restconf/service_types/builders/configure_slos.py @@ -25,23 +25,23 @@ def configure_slos(network_access, ietf_intent, layer_type): logging.debug(f"Configuring SLOs with constraints: {safe_get(ietf_intent, ['template', 'slo-policy', 'metric-bound'])}") - # Configurar constraints de métricas + # Configure metric constraints metric_bounds = safe_get(ietf_intent, ["template", "slo-policy", "metric-bound"]) if metric_bounds: for constraint in metric_bounds: apply_metric_constraint(service, qos_class, constraint, ietf_intent["id"], layer_type) - # Configurar availability + # Configure availability availability = safe_get(ietf_intent, ["template", "slo-policy", "availability"]) if availability: qos_class.setdefault("bandwidth", {})["guaranteed-bw-percent"] = availability - # Configurar MTU + # Configure MTU mtu = safe_get(ietf_intent, ["template", "slo-policy", "mtu"]) if mtu: service["svc-mtu"] = mtu - # Configurar availability y MTU por defecto si no se han configurado + # Configure availability and MTU defaults if not configured if "guaranteed-bw-percent" not in qos_class.get("bandwidth", {}): qos_class.setdefault("bandwidth", {})["guaranteed-bw-percent"] = 0 if "svc-mtu" not in service: diff --git a/src/realizer/restconf/service_types/builders/create_site_from_sdp.py b/src/realizer/restconf/service_types/builders/create_site_from_sdp.py index 5f05f42..124fbb2 100644 --- a/src/realizer/restconf/service_types/builders/create_site_from_sdp.py +++ b/src/realizer/restconf/service_types/builders/create_site_from_sdp.py @@ -34,7 +34,7 @@ def create_site_from_sdp(sdp, ietf_intent, connectivity_type, layer_type): """ logging.debug(f"Processing SDP: {sdp}") - # Extraer información básica + # Extract basic information location = safe_get(sdp, ["sdp", "node-id"]) router_id = safe_get(sdp, ["sdp", "attachment-circuits", "attachment-circuit", 0, "ac-node-id"]) router_if = safe_get(sdp, ["sdp", "attachment-circuits", "attachment-circuit", 0, "ac-tp-id"]) diff --git a/src/realizer/restconf/service_types/l2vpn.py b/src/realizer/restconf/service_types/l2vpn.py index 7f17235..465e86e 100644 --- a/src/realizer/restconf/service_types/l2vpn.py +++ b/src/realizer/restconf/service_types/l2vpn.py @@ -31,7 +31,7 @@ def l2vpn(ietf_intent): Returns: Diccionario con la configuración del servicio L2VPN o None si no hay SDPs """ - # Validación temprana + # Early validation if not ietf_intent.get("sdps"): logging.warning("SDPs not found in the intent. Skipping L2VPN realization.") return None diff --git a/src/realizer/restconf/service_types/l3vpn.py b/src/realizer/restconf/service_types/l3vpn.py index 1e73866..78a8493 100644 --- a/src/realizer/restconf/service_types/l3vpn.py +++ b/src/realizer/restconf/service_types/l3vpn.py @@ -29,7 +29,7 @@ def l3vpn(ietf_intent): Returns: Diccionario con la configuración del servicio L3VPN o None si no hay SDPs """ - # Validación temprana + # Early validation if not ietf_intent.get("sdps"): logging.warning("SDPs not found in the intent. Skipping L3VPN realization.") return None diff --git a/src/tests/test_e2e.py b/src/tests/test_e2e.py index 6e9f2e4..36ed12f 100644 --- a/src/tests/test_e2e.py +++ b/src/tests/test_e2e.py @@ -22,10 +22,10 @@ from src.api.main import Api from src.main import NSController from app import create_app -# Carpeta donde están los JSON de requests +# Folder where request JSONs are located REQUESTS_DIR = Path(__file__).parent / "requests" -# Lista de todos los flags booleanos que quieres probar +# List of all boolean flags you want to test FLAGS_TO_TEST = ["WEBUI_DEPLOY", "DUMP_TEMPLATES", "PLANNER_ENABLED", "PCE_EXTERNAL", "NRP_ENABLED"] # Valores posibles para PLANNER_TYPE @@ -67,7 +67,7 @@ def temp_sqlite_db(monkeypatch, tmp_path): if temp_db_path.exists(): temp_db_path.unlink() -# Función para cargar todos los JSONs +# Function to load all JSONs def load_request_files(): test_cases = [] for f in REQUESTS_DIR.glob("*.json"): @@ -76,7 +76,7 @@ def load_request_files(): test_cases.append(json_data) return test_cases -# Generador de todas las combinaciones de flags +# Generator of all flag combinations def generate_flag_combinations(): bool_values = [True, False] for combo in product(bool_values, repeat=len(FLAGS_TO_TEST)): @@ -85,7 +85,7 @@ def generate_flag_combinations(): yield {**bool_flags, "PLANNER_TYPE": planner_type} -# Fixture que combina cada request con cada combinación de flags +# Fixture that combines each request with each combination of flags def generate_test_cases(): requests = load_request_files() for json_data in requests: @@ -104,14 +104,14 @@ def test_add_and_delete_flow(app, json_data, flags, expected_codes, set_flags, t controller = NSController(controller_type="TFS") api = Api(controller) - # Añadir flujo + # Add flow data, code = api.add_flow(json_data) assert code in expected_codes, f"Flags en fallo: {flags}" - # Eliminar flujo si fue creado + # Delete flow if it was created if code == 201 and isinstance(data, dict) and "slice_id" in data: slice_id = data["slice_id"] _, delete_code = api.delete_flows(slice_id=slice_id) - assert delete_code == 204, f"No se pudo eliminar el slice {slice_id}" + assert delete_code == 204, f"Could not delete slice {slice_id}" diff --git a/src/tests/test_initialization.py b/src/tests/test_initialization.py index 3500465..629d590 100644 --- a/src/tests/test_initialization.py +++ b/src/tests/test_initialization.py @@ -16,7 +16,7 @@ import pytest -# Importa tu clase (ajusta el nombre del módulo si es distinto) +# Import your class (adjust the module name if different) from src.main import NSController def test_init_default_values(): @@ -47,7 +47,7 @@ def test_init_independence_between_instances(): # Modifico una lista en una instancia c1.response.append("test-response") - # La otra instancia no debería verse afectada + # The other instance should not be affected assert c2.response == [] assert c1.response == ["test-response"] diff --git a/src/tests/test_nbi_processor.py b/src/tests/test_nbi_processor.py index 40db65e..fecb109 100644 --- a/src/tests/test_nbi_processor.py +++ b/src/tests/test_nbi_processor.py @@ -45,7 +45,7 @@ def ietf_intent(): @pytest.fixture def gpp_intent(): - # Estructura mínima consistente con translator + # Minimum structure consistent with translator return { "RANSliceSubnet1": { "networkSliceSubnetRef": ["subnetA", "subnetB"] @@ -95,7 +95,7 @@ def gpp_intent(): @pytest.fixture def fake_template(): - # Plantilla mínima para que el traductor funcione + # Minimum template for translator to work return { "ietf-network-slice-service:network-slice-services": { "slo-sle-templates": { @@ -176,7 +176,7 @@ def test_detect_format_invalid_types(data): def test_detect_format_multiple_keys(): - # Si tiene IETF y 3GPP, debe priorizar IETF + # If it has IETF and 3GPP, should prioritize IETF data = { "ietf-network-slice-service:network-slice-services": {}, "RANSliceSubnet1": {} @@ -187,7 +187,7 @@ def test_detect_format_multiple_keys(): # ---------- Extra nbi_processor ---------- def test_nbi_processor_gpp_missing_refs(gpp_intent): - # Quitar networkSliceSubnetRef debería provocar ValueError en translator loop + # Removing networkSliceSubnetRef should cause ValueError in translator loop broken = gpp_intent.copy() broken["RANSliceSubnet1"] = {} # no tiene "networkSliceSubnetRef" with pytest.raises(KeyError): @@ -210,10 +210,10 @@ def test_translator_maps_metrics(mock_load_template, gpp_intent, fake_template): @patch("src.nbi_processor.translator.load_template") def test_translator_empty_profile(mock_load_template, gpp_intent, fake_template): mock_load_template.return_value = fake_template - gpp_intent["subnetA"]["SliceProfileList"] = [{}] # vacío + gpp_intent["subnetA"]["SliceProfileList"] = [{}] # empty result = translator(gpp_intent, "subnetA") metrics = result["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"] - assert metrics == [] # no debería añadir nada + assert metrics == [] # should not add anything @patch("src.nbi_processor.translator.load_template") def test_translator_sdps_are_populated(mock_load_template, gpp_intent, fake_template): diff --git a/src/webui/gui.py b/src/webui/gui.py index 73db9f1..933b8cf 100644 --- a/src/webui/gui.py +++ b/src/webui/gui.py @@ -181,7 +181,7 @@ def __datos_json(): @gui_bp.route('/webui') def home(): session['enter'] = False - # Leer las IPs actuales del archivo de configuración + # Read the current IPs from the configuration file try: tfs_ip = current_app.config["TFS_IP"] ixia_ip = current_app.config["IXIA_IP"] @@ -204,7 +204,7 @@ def generate_tfs(): slice_request = __build_request_ietf(src_node_ip=src_node_ip, dst_node_ip=dst_node_ip, vlan_id=vlan_id, latency=latency, bandwidth=bandwidth) - # Si 'request' es un diccionario, conviértelo a JSON + # If 'request' is a dictionary, convert it to JSON json_data = json.dumps(slice_request) files = { 'file': ('ietf_template_example.json', json_data, 'application/json') @@ -248,7 +248,7 @@ def generate_ixia(): if int(reliability)==100: reliability=None slice_request = __build_request_ietf(src_node_ip=src_node_ip, dst_node_ip=dst_node_ip, vlan_id=vlan_id, bandwidth=bandwidth, latency=latency, latency_version=latency_version, tolerance=tolerance, reliability=reliability) - # Si 'request' es un diccionario, conviértelo a JSON + # If 'request' is a dictionary, convert it to JSON json_data = json.dumps(slice_request) files = { 'file': ('ietf_template_example.json', json_data, 'application/json') -- GitLab From 5797e62632737783a1bbc3e82d7aaacf2414a2cf Mon Sep 17 00:00:00 2001 From: velazquez Date: Wed, 20 May 2026 10:08:23 +0200 Subject: [PATCH 2/2] More comments to english --- src/api/main.py | 34 ++++++------- src/mapper/process_connnectivity.py | 1 - .../builders/create_site_from_sdp.py | 14 +++--- src/realizer/restconf/service_types/l2vpn.py | 16 +++--- src/realizer/restconf/service_types/l3vpn.py | 16 +++--- src/tests/test_api.py | 46 +++++++++-------- src/tests/test_e2e.py | 30 ++++++------ src/tests/test_utils.py | 49 +++++++++---------- 8 files changed, 100 insertions(+), 106 deletions(-) diff --git a/src/api/main.py b/src/api/main.py index b672f96..eae484a 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -409,12 +409,12 @@ class Api: def update_network_slice_service(self, intent): """ - Modifica (reemplaza) toda la configuración de network-slice-services + Modify (replace) all network-slice-services configuration """ try: xpath = "/ietf-network-slice-service:network-slice-services" - # Verificar que existe algo que modificar + # Verify if there is something to modify existing_data = get_data_store(xpath) if not existing_data: return send_response(False, code=404, message="Network slice services not found") @@ -424,7 +424,7 @@ class Api: if not result: return send_response(False, code=500, message="Failed to process slice in TFS") - # Reemplazar completamente el recurso + # Replace completely the resource update_data_store(intent) logging.info("Network slice services modified successfully") @@ -442,17 +442,17 @@ class Api: def update_slo_sle_template(self, template_id, template): """ - Modifica (reemplaza) un template SLO/SLE específico + Modify (replace) an specific SLO/SLE template """ try: xpath = f"/ietf-network-slice-service:network-slice-services/slo-sle-templates/slo-sle-template[id='{template_id}']" - # Verificar que el template existe + # Verify the template exists existing_template = get_data_store(xpath) if not existing_template: return send_response(False, code=404, message="Template not found") - # Asegurar que el ID en el body coincide con el de la URL + # Assure that the body ID matches the URL if "id" in template and template["id"] != template_id: return send_response(False, code=400, message="Template ID in body does not match URL") @@ -478,7 +478,7 @@ class Api: template_data = template.copy() template_data.pop("id", None) - # Reemplazar el template + # Replace the template update_data_store(template_data, xpath) logging.info(f"Template {template_id} modified successfully") @@ -501,16 +501,16 @@ class Api: try: xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']" - # Verificar que el slice existe + # Verify that the slice exists existing_slice = get_data_store(xpath) if not existing_slice: return send_response(False, code=404, message="Slice not found") - # Asegurar que el ID en el body coincide con el de la URL + # Assure that the body ID matches the URL if "id" in intent and intent["id"] != slice_id: return send_response(False, code=400, message="Slice ID in body does not match URL") - # Validar que existe el template SLO/SLE referenciado + # Validate that the referenced SLO/SLE template exists if "slo-sle-template" in intent: template_ref = intent.get("slo-sle-template") xpath_template = f"/ietf-network-slice-service:network-slice-services/slo-sle-templates/slo-sle-template[id='{template_ref}']" @@ -518,7 +518,7 @@ class Api: if not existing_template: return send_response(False, code=404, message="Referenced SLO/SLE template not found") - # Construir el intent completo para TFS + # Build the full intent full_intent = { "ietf-network-slice-service:network-slice-services": { "slo-sle-templates": { @@ -548,7 +548,7 @@ class Api: intent_data = intent.copy() intent_data.pop("id", None) - # Reemplazar el slice + # Replace the slice update_data_store(intent_data, xpath) logging.info(f"Slice {slice_id} modified successfully") @@ -568,22 +568,22 @@ class Api: def update_sdp(self, slice_id, sdp_id, sdp): """ - Modifica (reemplaza) un SDP específico dentro de un slice + Modify (replace) an specific SDP in the slice """ try: - # Verificar que el slice existe + # Verify the template exists slice_xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']" existing_slice = get_data_store(slice_xpath) if not existing_slice: return send_response(False, code=404, message="Slice not found") - # Verificar que el SDP existe + # Verify the SDP exists sdp_xpath = f"/ietf-network-slice-service:network-slice-services/slice-service[id='{slice_id}']/sdps/sdp[id='{sdp_id}']" existing_sdp = get_data_store(sdp_xpath) if not existing_sdp: return send_response(False, code=404, message="SDP not found") - # Asegurar que el ID en el body coincide con el de la URL + # Assure that the body ID matches the URL if "id" in sdp and sdp["id"] != sdp_id: return send_response(False, code=400, message="SDP ID in body does not match URL") @@ -591,7 +591,7 @@ class Api: sdp_data = sdp.copy() sdp_data.pop("id", None) - # Reemplazar el SDP + # Replace the SDP update_data_store(sdp_data, sdp_xpath) logging.info(f"SDP {sdp_id} in slice {slice_id} modified successfully") diff --git a/src/mapper/process_connnectivity.py b/src/mapper/process_connnectivity.py index 8653124..b057ce1 100644 --- a/src/mapper/process_connnectivity.py +++ b/src/mapper/process_connnectivity.py @@ -16,7 +16,6 @@ from src.utils.safe_get import safe_get from .extract_sdp_info import extract_sdp_info -import logging def process_connectivity(connection_group_id, connectivity_type, connectivity_construct, connectivity_construct_id, slice_service): """ diff --git a/src/realizer/restconf/service_types/builders/create_site_from_sdp.py b/src/realizer/restconf/service_types/builders/create_site_from_sdp.py index 124fbb2..b854364 100644 --- a/src/realizer/restconf/service_types/builders/create_site_from_sdp.py +++ b/src/realizer/restconf/service_types/builders/create_site_from_sdp.py @@ -22,15 +22,15 @@ from .configure_slos import configure_slos def create_site_from_sdp(sdp, ietf_intent, connectivity_type, layer_type): """ - Crea la configuración de un site a partir de un SDP. - + Creates the configuration of a site from an SDP. + Args: sdp: Service Delivery Point - ietf_intent: Intent IETF completo - connectivity_type: Tipo de conectividad - + ietf_intent: Complete IETF intent + connectivity_type: Connectivity type + Returns: - Diccionario con la configuración del site + Dictionary with site configuration """ logging.debug(f"Processing SDP: {sdp}") @@ -43,7 +43,7 @@ def create_site_from_sdp(sdp, ietf_intent, connectivity_type, layer_type): network_access = create_network_access(sdp, ietf_intent, connectivity_type, router_id, router_if, layer_type) - # Crear estructura del site + # Create site structure site = { "site-id": safe_get(sdp, ["sdp", "id"]), "locations": { diff --git a/src/realizer/restconf/service_types/l2vpn.py b/src/realizer/restconf/service_types/l2vpn.py index 465e86e..5d48474 100644 --- a/src/realizer/restconf/service_types/l2vpn.py +++ b/src/realizer/restconf/service_types/l2vpn.py @@ -22,26 +22,26 @@ from .builders.create_site_from_sdp import create_site_from_sdp def l2vpn(ietf_intent): """ - Crea un servicio L2VPN basado en el intent IETF proporcionado. - + Creates an L2VPN service based on the provided IETF intent. + Args: - ietf_intent: Diccionario con la configuración del intent IETF - response: Objeto de respuesta - + ietf_intent: Dictionary with IETF intent configuration + response: Response object + Returns: - Diccionario con la configuración del servicio L2VPN o None si no hay SDPs + Dictionary with L2VPN service configuration or None if no SDPs """ # Early validation if not ietf_intent.get("sdps"): logging.warning("SDPs not found in the intent. Skipping L2VPN realization.") return None - # Inicializar estructura L2VPN + # Initialize L2VPN structure connectivity_type = ietf_intent["connectivity_type"] l2_service = initialize_structure(ietf_intent["id"], connectivity_type, layer_type="l2") - # Procesar cada SDP + # Process each SDP for sdp in ietf_intent["sdps"]: site = create_site_from_sdp(sdp, ietf_intent, connectivity_type, layer_type="l2") l2_service["ietf-l2vpn-svc:l2vpn-svc"]["sites"]["site"].append(site) diff --git a/src/realizer/restconf/service_types/l3vpn.py b/src/realizer/restconf/service_types/l3vpn.py index 78a8493..2fdf7bb 100644 --- a/src/realizer/restconf/service_types/l3vpn.py +++ b/src/realizer/restconf/service_types/l3vpn.py @@ -20,25 +20,25 @@ from .builders.create_site_from_sdp import create_site_from_sdp def l3vpn(ietf_intent): """ - Crea un servicio L3VPN basado en el intent IETF proporcionado. - + Creates an L3VPN service based on the provided IETF intent. + Args: - ietf_intent: Diccionario con la configuración del intent IETF - response: Objeto de respuesta - + ietf_intent: Dictionary with IETF intent configuration + response: Response object + Returns: - Diccionario con la configuración del servicio L3VPN o None si no hay SDPs + Dictionary with L3VPN service configuration or None if no SDPs """ # Early validation if not ietf_intent.get("sdps"): logging.warning("SDPs not found in the intent. Skipping L3VPN realization.") return None - # Inicializar estructura L3VPN + # Initialize L3VPN structure connectivity_type = ietf_intent["connectivity_type"] l3_service = initialize_structure(ietf_intent["id"], connectivity_type, layer_type="l3") - # Procesar cada SDP + # Process each SDP for sdp in ietf_intent["sdps"]: site = create_site_from_sdp(sdp, ietf_intent, connectivity_type, layer_type="l3") l3_service["ietf-l3vpn-svc:l3vpn-svc"]["sites"]["site"].append(site) diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 353198f..61d5151 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -32,7 +32,7 @@ load_dotenv() @pytest.fixture(scope="session") def flask_app(): - """Crea una app Flask mínima para los tests.""" + """Creates a minimal Flask app for tests.""" app = Flask(__name__) app.config.update({ "TESTING": True, @@ -54,13 +54,13 @@ def flask_app(): @pytest.fixture(autouse=True) def push_flask_context(flask_app): - """Empuja automáticamente un contexto Flask para cada test.""" + """Automatically pushes a Flask context for each test.""" with flask_app.app_context(): yield @pytest.fixture def temp_db(tmp_path): - """Fixture to create and cleanup test database using SQLite instead of JSON.""" + """Fixture to create and cleanup a test database using SQLite instead of JSON.""" test_db_name = str(tmp_path / "test_slice.db") # Create database with proper schema @@ -111,14 +111,14 @@ def env_variables(): @pytest.fixture def controller_with_mocked_db(temp_db): - """Crea un NSController con base de datos mockeada.""" + """Creates an NSController with a mocked database.""" with patch('src.database.db.DB_NAME', temp_db): yield NSController(controller_type="TFS") @pytest.fixture def ietf_intent(): - """Intent válido en formato IETF.""" + """Valid intent in IETF format.""" return { "ietf-network-slice-service:network-slice-services": { "slo-sle-templates": { @@ -150,8 +150,8 @@ def ietf_intent(): { "match-type": [ { - "type": "vlan", - "vlan": [100] + "type": "vlan", + "vlan": [100] } ] } @@ -175,8 +175,8 @@ def ietf_intent(): { "match-type": [ { - "type": "vlan", - "vlan": [100] + "type": "vlan", + "vlan": [100] } ] } @@ -205,14 +205,14 @@ class TestBasicApiOperations: """Tests for basic API operations.""" def test_get_flows_empty(self, controller_with_mocked_db): - """Debe devolver error cuando no hay slices.""" + """Should return an error when there are no slices.""" result, code = Api(controller_with_mocked_db).get_flows() assert code == 404 assert result["success"] is False assert result["data"] is None def test_add_flow_success(self, controller_with_mocked_db, ietf_intent): - """Debe poder añadir un flow exitosamente.""" + """Should successfully add a flow.""" with patch('src.database.db.save_data') as mock_save: result, code = Api(controller_with_mocked_db).add_flow(ietf_intent) assert code == 201 @@ -220,7 +220,7 @@ class TestBasicApiOperations: assert "slices" in result["data"] def test_add_and_get_flow(self, controller_with_mocked_db, ietf_intent): - """Debe poder añadir un flow y luego recuperarlo.""" + """Should add a flow and then retrieve it.""" with patch('src.database.db.save_data') as mock_save, \ patch('src.database.db.get_all_data') as mock_get_all: @@ -239,7 +239,7 @@ class TestBasicApiOperations: assert any(s["slice_id"] == "slice-test-1" for s in flows) def test_modify_flow_success(self, controller_with_mocked_db, ietf_intent): - """Debe poder modificar un flow existente.""" + """Should successfully modify an existing flow.""" with patch('src.database.db.update_data') as mock_update: Api(controller_with_mocked_db).add_flow(ietf_intent) new_intent = ietf_intent.copy() @@ -251,7 +251,7 @@ class TestBasicApiOperations: assert result["success"] is True def test_delete_specific_flow_success(self, controller_with_mocked_db, ietf_intent): - """Debe borrar un flow concreto.""" + """Should delete a specific flow.""" with patch('src.database.db.delete_data') as mock_delete: Api(controller_with_mocked_db).add_flow(ietf_intent) result, code = Api(controller_with_mocked_db).delete_flows("slice-test-1") @@ -259,14 +259,14 @@ class TestBasicApiOperations: assert result == {} def test_delete_all_flows_success(self, controller_with_mocked_db): - """Debe borrar todos los flows.""" + """Should delete all flows.""" with patch('src.database.db.delete_all_data') as mock_delete_all: result, code = Api(controller_with_mocked_db).delete_flows() assert code == 204 assert result == {} def test_get_specific_flow(self, controller_with_mocked_db, ietf_intent): - """Debe poder recuperar un flow específico.""" + """Should retrieve a specific flow.""" with patch('src.database.db.get_data') as mock_get: Api(controller_with_mocked_db).add_flow(ietf_intent) mock_get.return_value = { @@ -284,19 +284,19 @@ class TestErrorHandling: """Tests for error handling.""" def test_add_flow_with_empty_intent(self, controller_with_mocked_db): - """Debe fallar si se pasa un intent vacío.""" + """Should fail if an empty intent is provided.""" result, code = Api(controller_with_mocked_db).add_flow({}) assert code in (400, 404, 500) assert result["success"] is False def test_add_flow_with_none(self, controller_with_mocked_db): - """Debe fallar si se pasa None como intent.""" + """Should fail if None is provided as intent.""" result, code = Api(controller_with_mocked_db).add_flow(None) assert code in (400, 500) assert result["success"] is False def test_get_nonexistent_slice(self, controller_with_mocked_db): - """Debe devolver 404 si se pide un slice inexistente.""" + """Should return 404 if a nonexistent slice is requested.""" with patch('src.database.db.get_data') as mock_get: mock_get.side_effect = ValueError("No slice found") @@ -305,7 +305,7 @@ class TestErrorHandling: assert result["success"] is False def test_modify_nonexistent_flow(self, controller_with_mocked_db, ietf_intent): - """Debe fallar si se intenta modificar un flow inexistente.""" + """Should fail if attempting to modify a nonexistent flow.""" with patch('src.database.db.update_data') as mock_update: mock_update.side_effect = ValueError("No slice found") @@ -314,12 +314,10 @@ class TestErrorHandling: assert result["success"] is False def test_delete_nonexistent_flow(self, controller_with_mocked_db): - """Debe fallar si se intenta eliminar un flow inexistente.""" + """Should fail if attempting to delete a nonexistent flow.""" with patch('src.database.db.delete_data') as mock_delete: mock_delete.side_effect = ValueError("No slice found") result, code = Api(controller_with_mocked_db).delete_flows("nonexistent") assert code == 404 - assert result["success"] is False - - + assert result["success"] is False \ No newline at end of file diff --git a/src/tests/test_e2e.py b/src/tests/test_e2e.py index 36ed12f..ce2e3e8 100644 --- a/src/tests/test_e2e.py +++ b/src/tests/test_e2e.py @@ -22,30 +22,30 @@ from src.api.main import Api from src.main import NSController from app import create_app -# Folder where request JSONs are located +# Folder where request JSON files are located REQUESTS_DIR = Path(__file__).parent / "requests" -# List of all boolean flags you want to test +# List of all boolean flags to test FLAGS_TO_TEST = ["WEBUI_DEPLOY", "DUMP_TEMPLATES", "PLANNER_ENABLED", "PCE_EXTERNAL", "NRP_ENABLED"] -# Valores posibles para PLANNER_TYPE +# Possible values for PLANNER_TYPE PLANNER_TYPE_VALUES = ["ENERGY", "HRAT", "TFS_OPTICAL"] @pytest.fixture def app(temp_sqlite_db): - """Crea la app Flask con configuración por defecto.""" + """Creates the Flask app with default configuration.""" app = create_app() return app @pytest.fixture def client(app): - """Cliente de test de Flask para hacer requests.""" + """Flask test client for making requests.""" return app.test_client() @pytest.fixture def set_flags(app): - """Cambia directamente los flags en app.config""" + """Directly updates flags in app.config.""" def _set(flags: dict): for k, v in flags.items(): app.config[k] = v @@ -53,21 +53,21 @@ def set_flags(app): @pytest.fixture def temp_sqlite_db(monkeypatch, tmp_path): - """Usa una base de datos SQLite temporal durante los tests.""" + """Uses a temporary SQLite database during tests.""" temp_db_path = tmp_path / "test_slice.db" monkeypatch.setattr("src.database.db.DB_NAME", str(temp_db_path)) - # Inicializa la base de datos temporal + # Initialize temporary database from src.database.db import init_db init_db() yield temp_db_path - # Limpieza al finalizar + # Cleanup after finishing if temp_db_path.exists(): temp_db_path.unlink() -# Function to load all JSONs +# Function to load all JSON files def load_request_files(): test_cases = [] for f in REQUESTS_DIR.glob("*.json"): @@ -76,7 +76,7 @@ def load_request_files(): test_cases.append(json_data) return test_cases -# Generator of all flag combinations +# Generator for all flag combinations def generate_flag_combinations(): bool_values = [True, False] for combo in product(bool_values, repeat=len(FLAGS_TO_TEST)): @@ -85,7 +85,7 @@ def generate_flag_combinations(): yield {**bool_flags, "PLANNER_TYPE": planner_type} -# Fixture that combines each request with each combination of flags +# Fixture combining each request with each flag combination def generate_test_cases(): requests = load_request_files() for json_data in requests: @@ -106,12 +106,10 @@ def test_add_and_delete_flow(app, json_data, flags, expected_codes, set_flags, t # Add flow data, code = api.add_flow(json_data) - assert code in expected_codes, f"Flags en fallo: {flags}" + assert code in expected_codes, f"Failed flags: {flags}" # Delete flow if it was created if code == 201 and isinstance(data, dict) and "slice_id" in data: slice_id = data["slice_id"] _, delete_code = api.delete_flows(slice_id=slice_id) - assert delete_code == 204, f"Could not delete slice {slice_id}" - - + assert delete_code == 204, f"Could not delete slice {slice_id}" \ No newline at end of file diff --git a/src/tests/test_utils.py b/src/tests/test_utils.py index 64d26bf..c8da1b3 100644 --- a/src/tests/test_utils.py +++ b/src/tests/test_utils.py @@ -16,8 +16,6 @@ import json import pytest -import os - from src.utils.load_template import load_template from src.utils.dump_templates import dump_templates from src.utils.send_response import send_response @@ -26,7 +24,7 @@ from flask import Flask @pytest.fixture def tmp_json_file(tmp_path): - """Crea un archivo JSON temporal válido y devuelve su ruta y contenido.""" + """Creates a valid temporary JSON file and returns its path and content.""" data = {"name": "test"} file_path = tmp_path / "template.json" file_path.write_text(json.dumps(data)) @@ -34,14 +32,14 @@ def tmp_json_file(tmp_path): def test_load_template_ok(tmp_json_file): - """Debe cargar correctamente un JSON válido.""" + """Should correctly load a valid JSON file.""" file_path, expected = tmp_json_file result = load_template(str(file_path)) assert result == expected def test_load_template_invalid(tmp_path): - """Debe devolver un response con error si el JSON es inválido.""" + """Should return an error response if the JSON is invalid.""" bad_file = tmp_path / "bad.json" bad_file.write_text("{invalid json}") @@ -51,7 +49,7 @@ def test_load_template_invalid(tmp_path): assert "Template loading error" in result["error"] def test_dump_templates_enabled(monkeypatch, tmp_path): - """Debe volcar múltiples JSON correctamente en src/templates si DUMP_TEMPLATES está activado.""" + """Should correctly dump multiple JSON files into src/templates when DUMP_TEMPLATES is enabled.""" templates_dir = tmp_path / "src" / "templates" templates_dir.mkdir(parents=True) @@ -73,7 +71,7 @@ def test_dump_templates_enabled(monkeypatch, tmp_path): assert json.loads(file_path.read_text()) == data def test_dump_templates_disabled(monkeypatch, tmp_path): - """No debe escribir nada en src/templates si DUMP_TEMPLATES está desactivado.""" + """Should not write anything into src/templates when DUMP_TEMPLATES is disabled.""" templates_dir = tmp_path / "src" / "templates" templates_dir.mkdir(parents=True) @@ -89,7 +87,7 @@ def test_dump_templates_disabled(monkeypatch, tmp_path): assert not (templates_dir / name).exists() def test_send_response_success(): - """Debe devolver success=True y code=200 si el resultado es True.""" + """Should return success=True and code=200 if the result is True.""" resp, code = send_response(True, data={"k": "v"}) assert code == 200 assert resp["success"] is True @@ -98,15 +96,15 @@ def test_send_response_success(): def test_send_response_error(): - """Debe devolver success=False y code=400 si el resultado es False.""" - resp, code = send_response(False, message="fallo") + """Should return success=False and code=400 if the result is False.""" + resp, code = send_response(False, message="failure") assert code == 400 assert resp["success"] is False assert resp["data"] is None - assert "fallo" in resp["error"] + assert "failure" in resp["error"] def ietf_intent(): - """Intento válido en formato IETF simplificado.""" + """Valid simplified IETF intent.""" return { "ietf-network-slice-service:network-slice-services": { "slo-sle-templates": { @@ -136,13 +134,14 @@ def ietf_intent(): "id": "CU", "sdp-ip-address": "10.0.0.1", "service-match-criteria": { - "match-criterion": [{ + "match-criterion": [{ "match-type": [ { - "type": "vlan", - "vlan": [100] + "type": "vlan", + "vlan": [100] } - ]}] + ] + }] }, }, { @@ -151,12 +150,12 @@ def ietf_intent(): "service-match-criteria": { "match-criterion": [{ "match-type": [ - { + { "type": "vlan", "vlan": [100] - } - ] - }] + } + ] + }] }, }, ] @@ -168,7 +167,7 @@ def ietf_intent(): def test_build_response_ok(): - """Debe construir correctamente el response a partir de un intent IETF válido.""" + """Should correctly build the response from a valid IETF intent.""" intent = ietf_intent() response = [] result = build_response(intent, response) @@ -182,7 +181,7 @@ def test_build_response_ok(): assert slice_data["destination"] == "DU" assert slice_data["vlan"] == 100 - # Validar constraints + # Validate constraints requirements = slice_data["requirements"] assert any(r["constraint_type"] == "one-way-bandwidth[kbps]" and r["constraint_value"] == "1000" for r in requirements) assert any(r["constraint_type"] == "availability[%]" and r["constraint_value"] == "99.9" for r in requirements) @@ -190,7 +189,7 @@ def test_build_response_ok(): def test_build_response_empty_policy(): - """Debe devolver lista sin constraints si slo-policy está vacío.""" + """Should return a list without constraints if slo-policy is empty.""" intent = ietf_intent() intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"] = {} response = [] @@ -201,11 +200,11 @@ def test_build_response_empty_policy(): def test_build_response_invalid_intent(): - """Debe fallar limpiamente si el intent no tiene la estructura esperada.""" + """Should fail gracefully if the intent does not have the expected structure.""" bad_intent = {} response = [] try: result = build_response(bad_intent, response) except Exception: result = [] - assert result == [] + assert result == [] \ No newline at end of file -- GitLab