diff --git a/manifests/deviceservice.yaml b/manifests/deviceservice.yaml index e49ba23995b07f4e7e956a69f8d5383c8e976e89..3c9a941e0ac67e752d5461c4859a7b03d3692a62 100644 --- a/manifests/deviceservice.yaml +++ b/manifests/deviceservice.yaml @@ -39,7 +39,7 @@ spec: - containerPort: 9192 env: - name: LOG_LEVEL - value: "INFO" + value: "DEBUG" startupProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:2020"] diff --git a/manifests/serviceservice.yaml b/manifests/serviceservice.yaml index 1dd383d615bb167ae6de15ae03c404a50bdee942..bcfb47ee392d86b8a46d22e58f8efdd6e10da329 100644 --- a/manifests/serviceservice.yaml +++ b/manifests/serviceservice.yaml @@ -36,7 +36,7 @@ spec: - containerPort: 9192 env: - name: LOG_LEVEL - value: "INFO" + value: "DEBUG" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:3030"] diff --git a/manifests/webuiservice.yaml b/manifests/webuiservice.yaml index a519aa4a2f8a1e81f1b7f2a1be1965ec0b8bb386..d25f5f8873d4cd3df8206fe88ec0e5805032c3fb 100644 --- a/manifests/webuiservice.yaml +++ b/manifests/webuiservice.yaml @@ -39,7 +39,7 @@ spec: - containerPort: 8004 env: - name: LOG_LEVEL - value: "INFO" + value: "DEBUG" - name: WEBUISERVICE_SERVICE_BASEURL_HTTP value: "/webui/" readinessProbe: diff --git a/my_deploy.sh b/my_deploy.sh index dbaaaa527a44b67281cc17d842ac1a2405a69af4..10f15bbbddc775273a72eb7b739bc6d9a8786d5e 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -106,7 +106,7 @@ export CRDB_DATABASE="tfs" export CRDB_DEPLOY_MODE="single" # Disable flag for dropping database, if it exists. -export CRDB_DROP_DATABASE_IF_EXISTS="" +export CRDB_DROP_DATABASE_IF_EXISTS="YES" # Disable flag for re-deploying CockroachDB from scratch. export CRDB_REDEPLOY="" @@ -154,7 +154,7 @@ export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" # Disable flag for dropping tables if they exist. -export QDB_DROP_TABLES_IF_EXIST="" +export QDB_DROP_TABLES_IF_EXIST="YES" # Disable flag for re-deploying QuestDB from scratch. export QDB_REDEPLOY="" diff --git a/proto/context.proto b/proto/context.proto index 87f69132df022e2aa4a0766dc9f0a7a7fae36d59..9cfe8fe07c87926530f8973c964d68dd0d3fac83 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -300,6 +300,7 @@ enum ServiceTypeEnum { SERVICETYPE_TE = 4; SERVICETYPE_E2E = 5; SERVICETYPE_OPTICAL_CONNECTIVITY = 6; + SERVICETYPE_IPLINK = 7; } enum ServiceStatusEnum { @@ -512,11 +513,17 @@ message ConfigRule_ACL { acl.AclRuleSet rule_set = 2; } +message ConfigRule_IP_LIK { + EndPointId endpoint_id = 1; + string subnet_ip = 2; +} + message ConfigRule { ConfigActionEnum action = 1; oneof config_rule { - ConfigRule_Custom custom = 2; - ConfigRule_ACL acl = 3; + ConfigRule_Custom custom = 2; + ConfigRule_ACL acl = 3; + ConfigRule_IP_LIK ip_link = 4; } } diff --git a/src/context/service/database/ConfigRule.py b/src/context/service/database/ConfigRule.py index 7d816b3e87803f71678511f4fadc6bbe7eba548e..0f204c505005fdfe84755c3d179f3cea56c75355 100644 --- a/src/context/service/database/ConfigRule.py +++ b/src/context/service/database/ConfigRule.py @@ -68,6 +68,9 @@ def compose_config_rules_data( _, _, endpoint_uuid = endpoint_get_uuid(config_rule.acl.endpoint_id, allow_random=False) rule_set_name = config_rule.acl.rule_set.name configrule_name = '{:s}:{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid, rule_set_name) + elif kind == ConfigRuleKindEnum.IP_LINK: + _, _, endpoint_uuid = endpoint_get_uuid(config_rule.ip_link.endpoint_id, allow_random=False) + configrule_name = '{:s}:{:s}:{:s}'.format(parent_kind, kind.value, endpoint_uuid) else: MSG = 'Name for ConfigRule({:s}) cannot be inferred '+\ '(device_uuid={:s}, service_uuid={:s}, slice_uuid={:s})' diff --git a/src/context/service/database/Link.py b/src/context/service/database/Link.py index 8aa2563e4b3bb5b46ffdefe4644cdfe321f1e734..87ec27eb8bc46945807c5b902638fd8414b027ee 100644 --- a/src/context/service/database/Link.py +++ b/src/context/service/database/Link.py @@ -74,12 +74,12 @@ def link_set(db_engine : Engine, messagebroker : MessageBroker, request : Link) related_topologies : List[Dict] = list() # By default, always add link to default Context/Topology - _,topology_uuid = topology_get_uuid(TopologyId(), allow_random=False, allow_default=True) - related_topologies.append({ - 'topology_uuid': topology_uuid, - 'link_uuid' : link_uuid, - }) - topology_uuids.add(topology_uuid) + # _,topology_uuid = topology_get_uuid(TopologyId(), allow_random=False, allow_default=True) + # related_topologies.append({ + # 'topology_uuid': topology_uuid, + # 'link_uuid' : link_uuid, + # }) + # topology_uuids.add(topology_uuid) link_endpoints_data : List[Dict] = list() for i,endpoint_id in enumerate(request.link_endpoint_ids): diff --git a/src/context/service/database/models/ConfigRuleModel.py b/src/context/service/database/models/ConfigRuleModel.py index f57c90b82b950e68103e1381c6ff0b118e6307df..73a667e6bc1d19a8182ff3f836e872c42766728d 100644 --- a/src/context/service/database/models/ConfigRuleModel.py +++ b/src/context/service/database/models/ConfigRuleModel.py @@ -21,8 +21,9 @@ from ._Base import _Base # Enum values should match name of field in ConfigRule message class ConfigRuleKindEnum(enum.Enum): - CUSTOM = 'custom' - ACL = 'acl' + CUSTOM = 'custom' + ACL = 'acl' + IP_LINK = 'ip_link' class DeviceConfigRuleModel(_Base): __tablename__ = 'device_configrule' diff --git a/src/device/requirements.in b/src/device/requirements.in index 73ea741d16dcdafd7a9be87ad79b457ccb6c5d5e..6f20b0de1c62eee000a22244f38b2ab0fd4aefd5 100644 --- a/src/device/requirements.in +++ b/src/device/requirements.in @@ -23,7 +23,7 @@ Flask==2.1.3 Flask-HTTPAuth==4.5.0 Flask-RESTful==0.3.9 Jinja2==3.0.3 -ncclient==0.6.13 +ncclient==0.6.15 p4runtime==1.3.0 pandas==1.5.* paramiko==2.9.2 diff --git a/src/device/service/drivers/openconfig/OpenConfigDriver.py b/src/device/service/drivers/openconfig/OpenConfigDriver.py index fd36e2dc40e38a125f1812f00eeb304106a40c8a..936fb94391bbb6522a26f75d9c7237bf514b3ad5 100644 --- a/src/device/service/drivers/openconfig/OpenConfigDriver.py +++ b/src/device/service/drivers/openconfig/OpenConfigDriver.py @@ -210,6 +210,8 @@ def edit_config( commit_per_rule=False, target='running', default_operation='merge', test_option=None, error_option=None, format='xml' # pylint: disable=redefined-builtin ): + logger.debug('Reglas mandadas = {:s}'.format(str(resources))) + str_method = 'DeleteConfig' if delete else 'SetConfig' results = [] if "L2VSI" in resources[0][1] and netconf_handler.vendor == "CISCO": diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json new file mode 100644 index 0000000000000000000000000000000000000000..e57011be614cc6af8bc011b4fd8581e86f0856c5 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json @@ -0,0 +1,231 @@ +{ + "l3vpn-svc": { + "vpn-services": { + "vpn-service": [ + { + "vpn-id": "vpn2" + } + ] + }, + "sites": { + "site": [ + { + "site-id": "site_OLT", + "locations": { + "location": [ + { + "location-id": "OLT" + } + ] + }, + "devices": { + "device": [ + { + "device-id": "128.32.33.5", + "location": "OLT" + } + ] + }, + "management": { + "type": "ietf-l3vpn-svc:provider-managed" + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": [ + { + "lan": "128.32.10.0/24", + "next-hop": "128.32.33.2", + "lan-tag": "vlan31" + }, + { + "lan": "128.32.20.0/24", + "next-hop": "128.32.33.2", + "lan-tag": "vlan31" + } + ] + } + } + } + ] + }, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": "500", + "site-network-access-type": "ietf-l3vpn-svc:multipoint", + "device-reference": "128.32.33.5", + "ip-connection": { + "ipv4": { + "address-allocation-type": "ietf-l3vpn-svc:static-address", + "addresses": { + "provider-address": "128.32.33.254", + "customer-address": "128.32.33.2", + "prefix-length": 24 + } + } + }, + "service": { + "svc-input-bandwidth": 1000000000, + "svc-output-bandwidth": 1000000000, + "svc-mtu": 1500, + "qos": { + "qos-profile": { + "classes": { + "class": [ + { + "class-id": "qos-realtime", + "direction": "ietf-l3vpn-svc:both", + "latency": { + "latency-boundary": 10 + }, + "bandwidth": { + "guaranteed-bw-percent": 100.0 + } + } + ] + } + } + } + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": [ + { + "lan": "172.1.201.0/24", + "next-hop": "128.32.33.254", + "lan-tag": "vlan31" + } + ] + } + } + } + ] + }, + "vpn-attachment": { + "vpn-id": "vpn2", + "site-role": "ietf-l3vpn-svc:spoke-role" + } + } + ] + } + }, + { + "site-id": "site_POP", + "locations": { + "location": [ + { + "location-id": "POP" + } + ] + }, + "devices": { + "device": [ + { + "device-id": "172.10.33.5", + "location": "POP" + } + ] + }, + "management": { + "type": "ietf-l3vpn-svc:provider-managed" + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": [ + { + "lan": "172.1.201.0/24", + "next-hop": "172.10.33.2", + "lan-tag": "vlan201" + } + ] + } + } + } + ] + }, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": "500", + "site-network-access-type": "ietf-l3vpn-svc:multipoint", + "device-reference": "172.10.33.5", + "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 + } + } + }, + "service": { + "svc-input-bandwidth": 1000000000, + "svc-output-bandwidth": 1000000000, + "svc-mtu": 1500, + "qos": { + "qos-profile": { + "classes": { + "class": [ + { + "class-id": "qos-realtime", + "direction": "ietf-l3vpn-svc:both", + "latency": { + "latency-boundary": 10 + }, + "bandwidth": { + "guaranteed-bw-percent": 100.0 + } + } + ] + } + } + } + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": [ + { + "lan": "128.32.10.0/24", + "next-hop": "172.10.33.254", + "lan-tag": "vlan201" + }, + { + "lan": "128.32.20.0/24", + "next-hop": "172.10.33.254", + "lan-tag": "vlan201" + } + ] + } + } + } + ] + }, + "vpn-attachment": { + "vpn-id": "vpn2", + "site-role": "ietf-l3vpn-svc:hub-role" + } + } + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/FunctionDefinition.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/FunctionDefinition.py new file mode 100644 index 0000000000000000000000000000000000000000..2a42d2578efd7194a713804d8fe6a44bea3ec36a --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/FunctionDefinition.py @@ -0,0 +1,426 @@ +from typing import Dict, List + + +class FunctionDefinition: + + #Metodo para leer el campo vpn-services + @staticmethod + def readVpnServices(vpnServicesALeer: Dict) -> List: + # Acceder al campo 'vpn-service' que es un array de Dict con un campo vpn-id + vpn_service_list = vpnServicesALeer.get('vpn-service', [])#Utilizar get para manejar casos donde 'vpn-service' no está presente + vpn_ids = [] #Array con todos los ID de cada VPN + + # Iterar sobre los elementos de la lista + for vpn_serviceEnFor in vpn_service_list: + vpn_id : str = vpn_serviceEnFor.get('vpn-id') #obtengo vpn-id del json iterando + # baseDatos["vpn-id_" + str(i)] = vpn_id # Utilizar f-strings para formatear las claves + vpn_ids.append(vpn_id) + + return vpn_ids + + #Metodo para escribir en la baseDatos lo leido en vpn-services + @staticmethod + def writeVpnServices(listaVPNs : List, baseDatos : Dict) -> Dict: + + baseDatos["VPN_IDs"] = listaVPNs + #baseDatos.update({"VPN_IDs": vpn_ids}) #ambas lineas son validas para añadir al Dict + baseDatos["VPNs_Dict"] = {} + #Obtengo cada vpn-id del array y lo meto en VPNs_Dict + for clave in baseDatos["VPN_IDs"]: + baseDatos["VPNs_Dict"][clave] = {}#ya tengo un Dict asociado a cada clave + + return baseDatos #devuelve la baseDatos pasada por argumento pero actualizada + + #Lee el array site + @staticmethod + def readSiteArray(site: List, baseDatos: Dict) -> Dict: + print("Elementos en site: " + str(len(site))) + temp : Dict = {} #Diccionario temporal que guarda el ultimo site leido + temp2 : Dict = {} #Diccionario temporal que registra todos los sites leidos + lista_SitesID = [] #lista que almacenara los IDs de cada site del array + i = 1 + for siteElement in site: + temp = FunctionDefinition.readSiteElement(lista_SitesID, siteElement) #Leo un site y me quedo con ID y Dict + temp2.update({lista_SitesID[-1] : temp}) # Almaceno esos datos en temp2 + i += 1 + + res : Dict = {} + res["sitesIDs"] = lista_SitesID + res["sites_Dict"] = temp2 # A sites_Dict se le añaden todos los sites + return res + + #Lee un site + @staticmethod + def readSiteElement(listaID: List, siteElement: Dict) -> Dict: + res: Dict = {} + listaID.append(siteElement.get('site-id')) + res.update({"site-id": siteElement.get('site-id')}) + res.update({"management": siteElement.get("management")}) #revistar si está bien + res.update({"devices": FunctionDefinition.readDevices(siteElement.get('devices'))}) + res.update({"routing-protocol" : FunctionDefinition.readRoutingProtocols(siteElement.get('routing-protocols'))}) + res.update({"site-network-accesses" : FunctionDefinition.readSiteNetworkAccesses(siteElement.get('site-network-accesses'))}) + return res + #FALTAN MUCHOS CAMPOS A LEER EN site, como locations, site-diversity, maximum-routes, security, service, traffic-protection + + #Lee array de Devices. Si hay más de un device, cambiar código + @staticmethod + def readDevices(devices: Dict) -> Dict: + res: Dict = {} + devices_list: List = devices.get('device', []) # Cambié 'devices' por 'device' aquà + if len(devices_list) > 1: + i: int = 1 + for eachDevice in devices_list: + res.update({f"device-id {i}": eachDevice.get('device-id')}) + i += 1 + elif len(devices_list) == 1: + res.update({"device-id": devices_list[0].get('device-id')}) + return res + + @staticmethod + def readRoutingProtocols(routingProtocols : Dict) -> Dict: + res : Dict = {} #todo el dict routing-protocols + rpArray : List = routingProtocols.get("routing-protocol") #el array routing-protocol + res.update({"type":rpArray[0].get("type")})#escribo el tipo en el diccionario + tipoRoutingProtocol : str = rpArray[0].get("type")#obtiene string "ietf-l3vpn-svc:static" + tipoResumido : str = tipoRoutingProtocol.split(":")[1] #se queda con la palabra, ospf, bgp, static,... sin el ietf... + #print("El tipo de routingProtocol es: "+ str(tipoResumido)) + def case1(): + ospfEntrada : Dict = rpArray[0].get("ospf",{}) #Funciona solo en caso de que el array tenga un elemento + ospfIntermedio : Dict = {} + ospfIntermedio.update({"address-family":ospfEntrada.get("address-family",{})}) + ospfIntermedio.update({"area-address":ospfEntrada.get("area-address",{})}) + ospfIntermedio.update({"metric":ospfEntrada.get("metric",{})}) + arrayShamLinksEntrada : List = ospfEntrada.get("sham-links",[]) #el array + shamLinksFinal : Dict = {} + arrayShamLinksFinal : List = [] + for elemento in arrayShamLinksEntrada: + shamLinksFinal.update({"target-site":elemento.get("target-site")}) + shamLinksFinal.update({"metric":elemento.get("metric")}) + arrayShamLinksFinal.append(shamLinksFinal) + + ospfIntermedio.update({"sham-links":shamLinksFinal}) + + res.update({"ospf":ospfIntermedio}) + + def case2(): + bgpEntrada : Dict = rpArray[0].get("bgp",{}) + bgpSalida : Dict = {} + bgpSalida.update({"autonomous-system":bgpEntrada.get("autonomous-system")}) + bgpSalida.update({"address-family":bgpEntrada.get("address-family")}) + res.update({"bgp":bgpSalida}) + + def case3(): #SIMPLEMENTE COPIA, NO LEE. A IMPLEMENTAR + cascadedDict: Dict = rpArray[0].get("static", {}).get("cascaded-lan-prefixes", {}) + valueRoutingProtocol = cascadedDict + res.update({"static":valueRoutingProtocol}) + print("se ha copiado 'static' pero no se ha leido, falta implementar") + + def case4(): + ripEntrada : Dict = rpArray[0].get("rip",{}) + ripSalida : Dict = {} + ripSalida.update({"address-family":ripEntrada.get("address-family")}) + res.update({"rip":ripSalida}) + + def case5(): + vrrpEntrada : Dict = rpArray[0].get("vrrp",{}) + vrrpSalida : Dict = {} + vrrpSalida.update({"address-family":vrrpEntrada.get("address-family")}) + res.update({"vrrp":vrrpSalida}) + + def default(): + #return ValueError(f"Tipo de protocolo no compatible: {tipoResumido}") + print("TIPO DE PROTOCOLO NO VALIDO") + + switch = { + "ospf": case1, + "bgp": case2, + "static": case3, + "rip":case4, + "vrrp": case5 + } + #res.update({tipoResumido: switch.get(tipoResumido, default)()}) + switch.get(tipoResumido)() + # print("_______________________d____________"+str(res)) + return res + + + @staticmethod + def readSiteNetworkAccesses(siteNetworkAccesses : Dict) -> Dict: + siteNetworkAccessArray : List = siteNetworkAccesses.get("site-network-access") + res : Dict = {} + arrayDeIDs : List = [] + for siteNetworkAccess in siteNetworkAccessArray: + variableIncial : str = siteNetworkAccess.get("site-network-access-id") + variableIncial = variableIncial.replace(" ", "-")#ESTO LO HAGO PARA QUE NO HAYA PROBLEMAS CON LOS ESPACIOS A LA HORA DE LEER EL JSON + arrayDeIDs.append(variableIncial) + res.update({"site-network-accesses-IDs":arrayDeIDs}) + dictSiteNetworkAccesses : Dict = {} + + for site in arrayDeIDs: + dictDeCadaID : Dict = {} + + # try: + dictDeCadaID.update({"site-network-access-type":siteNetworkAccess.get("site-network-access-type")}) + dictDeCadaID.update({"site-role":siteNetworkAccess.get("vpn-attachment", {}).get("site-role")}) + #dictDeCadaID.update({"location-flavor" : FunctionDefinition.readLocationFlavor(siteNetworkAccess.get("location-flavor"))}) DE MOMENTO DA ERROR PORQUE ESE CAMPO NO EXISTE + #dictDeCadaID.update({"access-diversity" : FunctionDefinition.readAccessDiversity(siteNetworkAccess.get("access-diversity"))}) CAMPO NO EXISTE AUN + #dictDeCadaID.update({"bearer" : FunctionDefinition.readBearer(siteNetworkAccess.get("bearer"))}) CAMPO NO EXISTE AUN + dictDeCadaID.update({"ip-connection":FunctionDefinition.readIpConnection(siteNetworkAccess.get("ip-connection"))}) + dictDeCadaID.update({"routing-protocols":FunctionDefinition.readRoutingProtocols(siteNetworkAccess.get("routing-protocols"))}) + dictDeCadaID.update({"service":FunctionDefinition.readService(siteNetworkAccess.get("service"))}) + # except NoJSONFieldException as e: + # print(e) + + dictSiteNetworkAccesses.update({site:dictDeCadaID}) + + res.update({"site-network-accesses-Dict": dictSiteNetworkAccesses}) + return res + + @staticmethod + def readLocationFlavor(locationFlavor : Dict) -> Dict: + res : Dict = {} + + if "location" in locationFlavor: + res.update({"location-reference":locationFlavor.get("location", {}).get("location-reference")}) + elif "device" in locationFlavor: + res.update({"device-reference":locationFlavor.get("device", {}).get("device-reference")}) + else: + raise NoJSONFieldException("location-flavor") + + return res + + @staticmethod + def readAccessDiversity(accessDiversity : Dict) -> Dict: + res : Dict = {} + arrayGroupsIni : List = accessDiversity.get("groups",{}).get("group") #obtengo el array de objetos + arrayGroupsIDs : List = [] + for groupId in arrayGroupsIni: + arrayGroupsIDs.append(groupId.get("group-id")) + res.update({"groups":arrayGroupsIDs}) + + arrayConstraintsIni : List = accessDiversity.get("constraints", {}).get("constraint") + arrayConstraintsFin : List = [] + for constraint in arrayConstraintsIni: #recordar que constraint es un objeto dentro del array arrayConstraintsIni + dictConstraint : Dict = {} + dictConstraint.update({"constraint-type" : constraint.get("constraint-type")}) + dictTargetFlavor : Dict = constraint.get("target", {}).get("target-flavor") + + if "id" in dictTargetFlavor: + dictConstraint.update({"target-flavor": dictTargetFlavor.get("id")}) + elif "all-accesses" in dictTargetFlavor: + dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-accesses")}) + elif "all-groups" in dictTargetFlavor: + dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-groups")}) + else: + raise NoJSONFieldException("read-access-divesity>constraints>constraint>target>target-flavor") + arrayConstraintsFin.append(dictConstraint) + + res.update({"constraints":arrayConstraintsFin}) + return res + + @staticmethod + def readBearer(bearer : Dict) -> Dict: + res : Dict = {} + + res.update({"always-on":bearer.get("always-on")}) + res.update({"bearer-reference":bearer.get("bearer-reference")}) + res.update({"requested-type":bearer.get("requested-type",{}).get("requested-type")}) + res.update({"strict":bearer.get("requested-type",{}).get("strict")}) + + return res + + @staticmethod + def readIpConnection(ipConnection : Dict) -> Dict: #IPV4 E IPV6 TIENEN LOS MISMOS CAMPOS + res : Dict = {} + if ipConnection.get("ipv4") is not None: + res.update({"ipv4": FunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv4"))}) + elif ipConnection.get("ipv6") is not None: + res.update({"ipv6":FunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv6"))}) + else: + raise NoJSONFieldException("ip-connection>ipv4/6") + + bfd : Dict = ipConnection.get("oam",{}).get("bfd") + if bfd is not None: + oamFinal : Dict = {} + oamFinal.update({"enabled":bfd.get("enabled")}) + holdtime : Dict = bfd.get("holdtime") + holdtimeFinal : Dict = {} + if "fixed" in holdtime: + holdtimeFinal.update({"fixed":holdtime.get("fixed")}) + elif "profile" in holdtime: + holdtimeFinal.update({"profile":holdtime.get("profile")}) + else: + raise NoJSONFieldException("oam>bfd>holdtime") + + oamFinal.update({"holdtime":holdtimeFinal}) + res.update({"oam":oamFinal}) + + return res + + @staticmethod + def readIpv4_Ipv6(ipv : Dict) -> Dict : + res : Dict = {} + + res.update({"address-allocation-type":ipv.get("address-allocation-type")}) + + providerDHCP : Dict = ipv.get("provider-dhcp") #None si no existe ese campo, puede mejorarse eficiencia + if providerDHCP is not None: + providerDHCPFinal : Dict = {} + providerDHCPFinal.update({"provider-dhcp":providerDHCP.get("provider-address")}) + providerDHCPFinal.update({"prefix-length":providerDHCP.get("prefix-lenght")}) + addressAsign : Dict = providerDHCP.get("address-asign") + addressAsignFinal : Dict = {} + if "number" in addressAsign: + addressAsignFinal.update({"number":addressAsign.get("number")}) + elif "explicit" in addressAsign: + addressAsignFinal.update({"explicit":addressAsign.get("explicit")}) + else: + raise NoJSONFieldException("ipv4/6>address-asign") + + providerDHCPFinal.update({"address-asign":addressAsignFinal}) + res.update({"provider-dhcp":providerDHCPFinal}) + + + dhcpRelay : Dict = ipv.get("dhcp-relay") + if dhcpRelay is not None: + dhcpRelayFinal : Dict = {} + dhcpRelayFinal.update({"provider-address":dhcpRelay.get("provider-address")}) + dhcpRelayFinal.update({"prefix-lenght":dhcpRelay.get("prefix-lenght")}) + dhcpRelayFinal.update({"server-ip-address":dhcpRelay.get("customer-dhcp-servers",{}).get("server-ip-address")}) + res.update({"dhcp-relay":dhcpRelayFinal}) + + addresses : Dict = ipv.get("addresses") + if addresses is not None: + addressesFinal : Dict = {} + addressesFinal.update({"provider-address":addresses.get("provider-address")}) + addressesFinal.update({"customer-address":addresses.get("customer-address")}) + addressesFinal.update({"prefixe-length":addresses.get("prefixe-length")}) + res.update({"addresses":addressesFinal}) + + return res + + # @staticmethod + # def readSecurity(security : Dict) -> Dict: + # res : Dict = {} + + # res.update({"authentication" : security.get("authentication")}) + # encryption : Dict = security.get("encryption") + # encryptionFinal : Dict = {} + + + # return res + + #readService todavÃa me da error + @staticmethod + def readService(service : Dict) -> Dict : + res : Dict = {} + #try: + + res.update({"svc-input-bandwidth":service.get("svc-input-bandwidth")}) + res.update({"svc-output-bandwidth":service.get("svc-output-bandwidth")}) + res.update({"svc-mtu":service.get("svc-mtu")}) + qos : Dict = service.get("qos") + ruleArrayIni : List = qos.get("qos-classification-policy") + ruleArrayFin : List = [] + for elementRule in ruleArrayIni : + elementRuleFin : Dict = {} + elementRuleFin.update({"id":elementRule.get("id")}) + matchTypeIni : Dict = elementRule.get("match-type") + #CUIDADO, FALTAN MUCHOS CAMPOS DENTRO DE match-flow Y match-application POR PROGRAMAR + matchTypeFin : Dict = {} + if "match-flow" in matchTypeIni: + matchTypeFin.update({"match-flow":matchTypeIni}) + elif "match-application" in matchTypeIni: + matchTypeFin.update({"match-application" : matchTypeIni}) + #else: + #raise NoJSONFieldException("qos>qos-classification-policy>match-type") + + + elementRuleFin.update({"match-type":matchTypeFin}) + elementRuleFin.update({"target-class-id":elementRule.get("target-class-id")}) + ruleArrayFin.append(elementRuleFin) + res.update({"qos-classification-policy":ruleArrayFin}) + + #Clase que lee el campo custom varias lineas mas adelante + + def readCustom (custom : Dict) -> Dict: #custom es un Dict que es: {classes: [class-id: *, ..., latency: {flavor:{lowest:*///boundary:*}}],[...],[...]} + res : Dict = {} + + arrayClassesEntrada : List = custom.get("class") #un array con elementos indeterminados + arrayClassesFinal : List = [] + + for elementEntrada in arrayClassesEntrada: + elementFinal : Dict = {} + elementFinal.update({"class-id":elementEntrada.get("class-id")}) + elementFinal.update({"direction":elementEntrada.get("direction")}) + elementFinal.update({"rate-limit":elementEntrada.get("rate-limit")}) + #Campo latency---> + #latency: { + # flavor:{ + # lowest:* + # o quizás + # boundary:* + # } + # } + + latency : Dict = elementEntrada.get("latency") + jitter : Dict = elementEntrada.get("jitter") + if latency is not None: + if "use-lowest-latency" in latency: + elementFinal.update({"latency-flavor-lowest":latency.get("use-lowest-latency")}) + elif "latency-boundary" in latency: + elementFinal.update({"latency-flavor-boundary":latency.get("latency-boundary")}) + elif jitter is not None: + if "use-lowest-jitter" in jitter: + elementFinal.update({"jitter-flavor-lowest":jitter.get("use-lowest-jiter")}) + elif "latency-boundary" in jitter: + elementFinal.update({"jitter-flavor-boundary":jitter.get("latency-boundary")}) + else: + print("Debe haber un campo lowest o boundary dentro de jitter>flavor") + else: + print("FALTAN CAMPOS EN CUSTOM") + + bandwidthEntrada : Dict = elementEntrada.get("bandwidth") + bandwidthSalida : Dict = {} + bandwidthSalida.update({"guaranteed-bw-percent":bandwidthEntrada.get("guaranteed-bw-percent")}) + bandwidthSalida.update({"end-to-end":bandwidthEntrada.get("end-to-end")}) + elementFinal.update({"bandwidth":bandwidthSalida}) + + arrayClassesFinal.append(elementFinal) + + res.update({"latency-flavor":arrayClassesFinal}) + return res + #END READCUSTOM + + qosProfileEntrada : Dict = qos.get("qos-profile") + qosProfileFin : Dict = {} + + if "profile" in qosProfileEntrada: + qosProfileFin.update({"standard-profile":qosProfileEntrada.get("standard")}) + elif "classes" in qosProfileEntrada: + qosProfileFin.update({"custom-classes":readCustom(qosProfileEntrada.get("classes"))}) + else: + print("falta campo en qos>qos-profile") + #raise NoJSONFieldException("qos>qos-profile") + + res.update({"qos-profile":qosProfileFin}) + res.update({"carrierscarrier":qos.get("carrierscarrier")}) + multicastEntrada : Dict = qos.get("multicast") + multicastSalida : Dict = {} + multicastSalida.update({"multicast-site-type":multicastEntrada.get("multicast-site-type")}) + multicastAddressSalida : Dict = {} + multicastAddressSalida.update({"ipv4":multicastEntrada.get("multicast-address-family", {}).get("ipv4")}) + multicastAddressSalida.update({"ipv6":multicastEntrada.get("multicast-address-family", {}).get("ipv6")}) + multicastSalida.update({"multicast-address-family":multicastAddressSalida}) + multicastSalida.update({"protocol-type":multicastEntrada.get("protocol-type")}) + + res.update({"multicast":multicastSalida}) + + # except KeyError: + # print("falta alguna clave en service") + # except NoJSONFieldException as e: + # print(e) + # finally: + return res + + \ No newline at end of file diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py index f7329cb35666f423e85f99510e5f89a82e89b7f8..1a87ec910262df5671fd681c4205c13b4a5678f6 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Handlers.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, netaddr +import logging, netaddr, json from typing import Dict, List, Optional, Tuple from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import Service, ServiceStatusEnum, ServiceTypeEnum @@ -26,6 +26,9 @@ from common.tools.grpc.EndPointIds import update_endpoint_ids from context.client.ContextClient import ContextClient from service.client.ServiceClient import ServiceClient +from .ReadServiceFunctionDefinition import ReadServiceFunctionDefinition +from .WriteNetworkFunctionDefinition import WriteNetworkFunctionDefinition + LOGGER = logging.getLogger(__name__) def create_service( @@ -209,3 +212,55 @@ def process_site(site : Dict, errors : List[Dict]) -> None: network_accesses : List[Dict] = site['site-network-accesses']['site-network-access'] for network_access in network_accesses: process_site_network_access(site_id, network_access, site_static_routing, errors) + + +def convert_l3sm_to_l3nm(): + jsonRequest = open("/home/ubuntu/tfs-ctrl/src/nbi/tests/ietf_l3vpn_req_svc1.json", "r") #devuelve un file-object. "r" es por reading + output_file_path = "/home/ubuntu/tfs-ctrl/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/EstructuraIntermedia.json" + jsonRequestData : str = jsonRequest.read() #jsonRequestData es de tipo str. Para leer el contenido, usamos read() + + jParse : Dict = json.loads(jsonRequestData) #jParse es de tipo Dict. esto va a parsear jsonRequetData. + #cuando obtengamos el valor de una clave, y ese valor sea un array [], el tipo será 'list' + #in order to convert a jsonObject to an string, you can use .dumps + + ietf_l3vpn : Dict = jParse['ietf-l3vpn-svc:l3vpn-svc'] + + vpn_services : Dict = ietf_l3vpn['vpn-services'] + #print(vpn_services) + # vpn_service = vpn_services['vpn-service'] #ahora, al ser vpn-service un array, creo que la variable vpn_service es de tipo 'list' + + # print(type(vpn_service)) #list + # print("VPN_SERVICE: " + str(vpn_service)) + # print(vpn_service[0]) + # print(vpn_service[0]['vpn-id']) + + bd : Dict[any, any] = {} #Base datos de variables del JSON + + #vpn_id = MiClase.readVpnServices(vpn_services) #vpn_id es variable de tipo List[str] con todas las vpn posibles + + #try: + bd = ReadServiceFunctionDefinition.writeVpnServices( ReadServiceFunctionDefinition.readVpnServices(vpn_services) , bd) + #print(json.dumps(bd, indent=2)) + + sites : List = ietf_l3vpn['sites']['site'] #En sites se almacena el valor de la clave 'sites'. + #print(json.dumps(sites, indent=2)) + + #### PEQUEÑA CHAPUZA PARA CUANDO SOLO HAY UNA VPN #### + lista_VPNs : list = bd["VPN_IDs"] + bd["VPNs_Dict"][lista_VPNs[0]].update(ReadServiceFunctionDefinition.readSiteArray(sites, bd)) #vpn1.update Updatea el valor de vpn1 + #### # #### + + + # except Exception as e: #En principio llegaran valueError o NoJSONFieldException + # print(e) + + #print(json.dumps(bd, indent=2)) + + #YANG SUITE ENTRADA, SERVICE ->creo que IETF-NBI-TEF-DECEMBER-23; ietf-l3vpn-svc; edit-config; none selected + #YANG SUITE SALIDA, RED (RFC9182) -> IETF-NBI-TEF-DECEMBER-23; ietf-l3vpn-ntw; edit-config; none selected + #Codigo para crear archivo .json de salida + with open(output_file_path, 'w') as json_file: + json.dump(bd, json_file, indent=3) + + jsonL3VPN_net : Dict = WriteNetworkFunctionDefinition.write() + # print(json.dumps(jsonL3VPN_net, indent = 3)) diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Main.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Main.py new file mode 100644 index 0000000000000000000000000000000000000000..d215a73ae373420011e18f95f2d02fe4b5b47ddd --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/Main.py @@ -0,0 +1,6 @@ +from src.nbi.service.rest_server.nbi_plugins.ietf_l3vpn import L3VPN_Services +#from src.nbi.service.rest_server.nbi_plugins.ietf_l3vpn.L3VPN_Services import L3VPN_Services + + +service : L3VPN_Services = L3VPN_Services() +service.post() diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/ReadServiceFunctionDefinition.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/ReadServiceFunctionDefinition.py new file mode 100644 index 0000000000000000000000000000000000000000..3218a18ddd2d5205c170723b2adaaadf323caddd --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/ReadServiceFunctionDefinition.py @@ -0,0 +1,418 @@ +from typing import Dict, List + +class ReadServiceFunctionDefinition: + + #Metodo para leer el campo vpn-services + @staticmethod + def readVpnServices(vpnServicesALeer: Dict) -> List: + # Acceder al campo 'vpn-service' que es un array de Dict con un campo vpn-id + vpn_service_list = vpnServicesALeer.get('vpn-service', [])#Utilizar get para manejar casos donde 'vpn-service' no está presente + vpn_ids = [] #Array con todos los ID de cada VPN + + # Iterar sobre los elementos de la lista + for vpn_serviceEnFor in vpn_service_list: + vpn_id : str = vpn_serviceEnFor.get('vpn-id') #obtengo vpn-id del json iterando + # baseDatos["vpn-id_" + str(i)] = vpn_id # Utilizar f-strings para formatear las claves + vpn_ids.append(vpn_id) + + return vpn_ids + + #Metodo para escribir en la baseDatos lo leido en vpn-services + @staticmethod + def writeVpnServices(listaVPNs : List, baseDatos : Dict) -> Dict: + + baseDatos["VPN_IDs"] = listaVPNs + #baseDatos.update({"VPN_IDs": vpn_ids}) #ambas lineas son validas para añadir al Dict + baseDatos["VPNs_Dict"] = {} + #Obtengo cada vpn-id del array y lo meto en VPNs_Dict + for clave in baseDatos["VPN_IDs"]: + baseDatos["VPNs_Dict"][clave] = {}#ya tengo un Dict asociado a cada clave + + return baseDatos #devuelve la baseDatos pasada por argumento pero actualizada + + #Lee el array site + @staticmethod + def readSiteArray(site: List, baseDatos: Dict) -> Dict: + print("Elementos en site: " + str(len(site))) + temp : Dict = {} #Diccionario temporal que guarda el ultimo site leido + temp2 : Dict = {} #Diccionario temporal que registra todos los sites leidos + lista_SitesID = [] #lista que almacenara los IDs de cada site del array + i = 1 + for siteElement in site: + temp = ReadServiceFunctionDefinition.readSiteElement(lista_SitesID, siteElement) #Leo un site y me quedo con ID y Dict + temp2.update({lista_SitesID[-1] : temp}) # Almaceno esos datos en temp2 + i += 1 + + res : Dict = {} + res["sitesIDs"] = lista_SitesID + res["sites_Dict"] = temp2 # A sites_Dict se le añaden todos los sites + return res + + #Lee un site + @staticmethod + def readSiteElement(listaID: List, siteElement: Dict) -> Dict: + res: Dict = {} + listaID.append(siteElement.get('site-id')) + res.update({"site-id": siteElement.get('site-id')}) + res.update({"management": siteElement.get("management")}) #revistar si está bien + res.update({"locations":siteElement.get("locations")}) + res.update({"devices": ReadServiceFunctionDefinition.readDevices(siteElement.get('devices'))}) + res.update({"routing-protocol" : ReadServiceFunctionDefinition.readRoutingProtocols(siteElement.get('routing-protocols'))}) + res.update({"site-network-accesses" : ReadServiceFunctionDefinition.readSiteNetworkAccesses(siteElement.get('site-network-accesses'))}) + print("readSiteElement Finiquitaoooo") + return res + #FALTAN MUCHOS CAMPOS A LEER EN site, como site-diversity, maximum-routes, security, service, traffic-protection + + #Lee array de Devices. Si hay más de un device, cambiar código + @staticmethod + def readDevices(devices: Dict) -> Dict: + res: Dict = {} + devices_list: List = devices.get('device', []) # Cambié 'devices' por 'device' aquà + if len(devices_list) > 1: + i: int = 1 + for eachDevice in devices_list: + res.update({f"device-id {i}": eachDevice.get('device-id')}) + i += 1 + elif len(devices_list) == 1: + res.update({"device-id": devices_list[0].get('device-id')}) + return res + + @staticmethod + def readRoutingProtocols(routingProtocols : Dict) -> Dict: + res : Dict = {} #todo el dict routing-protocols + rpArray : List = routingProtocols.get("routing-protocol") #el array routing-protocol + res.update({"type":rpArray[0].get("type")})#escribo el tipo en el diccionario + tipoRoutingProtocol : str = rpArray[0].get("type")#obtiene string "ietf-l3vpn-svc:static" + tipoResumido : str = tipoRoutingProtocol.split(":")[1] #se queda con la palabra, ospf, bgp, static,... sin el ietf... + #print("El tipo de routingProtocol es: "+ str(tipoResumido)) + def case1(): + ospfEntrada : Dict = rpArray[0].get("ospf",{}) #Funciona solo en caso de que el array tenga un elemento + ospfIntermedio : Dict = {} + ospfIntermedio.update({"address-family":ospfEntrada.get("address-family",{})}) + ospfIntermedio.update({"area-address":ospfEntrada.get("area-address",{})}) + ospfIntermedio.update({"metric":ospfEntrada.get("metric",{})}) + arrayShamLinksEntrada : List = ospfEntrada.get("sham-links",[]) #el array + shamLinksFinal : Dict = {} + arrayShamLinksFinal : List = [] + for elemento in arrayShamLinksEntrada: + shamLinksFinal.update({"target-site":elemento.get("target-site")}) + shamLinksFinal.update({"metric":elemento.get("metric")}) + arrayShamLinksFinal.append(shamLinksFinal) + + ospfIntermedio.update({"sham-links":shamLinksFinal}) + + res.update({"ospf":ospfIntermedio}) + + def case2(): + bgpEntrada : Dict = rpArray[0].get("bgp",{}) + bgpSalida : Dict = {} + bgpSalida.update({"autonomous-system":bgpEntrada.get("autonomous-system")}) + bgpSalida.update({"address-family":bgpEntrada.get("address-family")}) + res.update({"bgp":bgpSalida}) + + def case3(): #SIMPLEMENTE COPIA, NO LEE. A IMPLEMENTAR + cascadedDict: Dict = rpArray[0].get("static", {}).get("cascaded-lan-prefixes", {}) + valueRoutingProtocol = cascadedDict + res.update({"static":valueRoutingProtocol}) + print("se ha copiado 'static' pero no se ha leido, falta implementar") + + def case4(): + ripEntrada : Dict = rpArray[0].get("rip",{}) + ripSalida : Dict = {} + ripSalida.update({"address-family":ripEntrada.get("address-family")}) + res.update({"rip":ripSalida}) + + def case5(): + vrrpEntrada : Dict = rpArray[0].get("vrrp",{}) + vrrpSalida : Dict = {} + vrrpSalida.update({"address-family":vrrpEntrada.get("address-family")}) + res.update({"vrrp":vrrpSalida}) + + def default(): + #return ValueError(f"Tipo de protocolo no compatible: {tipoResumido}") + print("TIPO DE PROTOCOLO NO VALIDO") + + switch = { + "ospf": case1, + "bgp": case2, + "static": case3, + "rip":case4, + "vrrp": case5 + } + #res.update({tipoResumido: switch.get(tipoResumido, default)()}) + switch.get(tipoResumido)() + # print("_______________________d____________"+str(res)) + print("read Routing Protocols finished") + return res + + + @staticmethod + def readSiteNetworkAccesses(siteNetworkAccesses : Dict) -> Dict: + siteNetworkAccessArray : List = siteNetworkAccesses.get("site-network-access") + res : Dict = {} + arrayDeIDs : List = [] + for siteNetworkAccess in siteNetworkAccessArray: + variableIncial : str = siteNetworkAccess.get("site-network-access-id") + variableIncial = variableIncial.replace(" ", "-")#ESTO LO HAGO PARA QUE NO HAYA PROBLEMAS CON LOS ESPACIOS A LA HORA DE LEER EL JSON + arrayDeIDs.append(variableIncial) + res.update({"site-network-accesses-IDs":arrayDeIDs}) + dictSiteNetworkAccesses : Dict = {} + + for site in arrayDeIDs: + dictDeCadaID : Dict = {} + + # try: + dictDeCadaID.update({"site-network-access-type":siteNetworkAccess.get("site-network-access-type")}) + dictDeCadaID.update({"device-reference":siteNetworkAccess.get("device-reference")}) + dictDeCadaID.update({"site-role":siteNetworkAccess.get("vpn-attachment", {}).get("site-role")}) + #dictDeCadaID.update({"location-flavor" : FunctionDefinition.readLocationFlavor(siteNetworkAccess.get("location-flavor"))}) DE MOMENTO DA ERROR PORQUE ESE CAMPO NO EXISTE + #dictDeCadaID.update({"access-diversity" : FunctionDefinition.readAccessDiversity(siteNetworkAccess.get("access-diversity"))}) CAMPO NO EXISTE AUN + #dictDeCadaID.update({"bearer" : FunctionDefinition.readBearer(siteNetworkAccess.get("bearer"))}) CAMPO NO EXISTE AUN + dictDeCadaID.update({"ip-connection":ReadServiceFunctionDefinition.readIpConnection(siteNetworkAccess.get("ip-connection"))}) + dictDeCadaID.update({"routing-protocols":ReadServiceFunctionDefinition.readRoutingProtocols(siteNetworkAccess.get("routing-protocols"))}) + dictDeCadaID.update({"service":ReadServiceFunctionDefinition.readService(siteNetworkAccess.get("service"))}) + # except NoJSONFieldException as e: + # print(e) + + dictSiteNetworkAccesses.update({site:dictDeCadaID}) + print("readSiteNetworkAccesses finiquitaoo") + res.update({"site-network-accesses-Dict": dictSiteNetworkAccesses}) + return res + + @staticmethod + def readLocationFlavor(locationFlavor : Dict) -> Dict: + res : Dict = {} + + if "location" in locationFlavor: + res.update({"location-reference":locationFlavor.get("location", {}).get("location-reference")}) + elif "device" in locationFlavor: + res.update({"device-reference":locationFlavor.get("device", {}).get("device-reference")}) + # else: + # raise NoJSONFieldException("location-flavor") + + return res + + @staticmethod + def readAccessDiversity(accessDiversity : Dict) -> Dict: + res : Dict = {} + arrayGroupsIni : List = accessDiversity.get("groups",{}).get("group") #obtengo el array de objetos + arrayGroupsIDs : List = [] + for groupId in arrayGroupsIni: + arrayGroupsIDs.append(groupId.get("group-id")) + res.update({"groups":arrayGroupsIDs}) + + arrayConstraintsIni : List = accessDiversity.get("constraints", {}).get("constraint") + arrayConstraintsFin : List = [] + for constraint in arrayConstraintsIni: #recordar que constraint es un objeto dentro del array arrayConstraintsIni + dictConstraint : Dict = {} + dictConstraint.update({"constraint-type" : constraint.get("constraint-type")}) + dictTargetFlavor : Dict = constraint.get("target", {}).get("target-flavor") + + if "id" in dictTargetFlavor: + dictConstraint.update({"target-flavor": dictTargetFlavor.get("id")}) + elif "all-accesses" in dictTargetFlavor: + dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-accesses")}) + elif "all-groups" in dictTargetFlavor: + dictConstraint.update({"target-flavor": dictTargetFlavor.get("all-groups")}) + # else: + # raise NoJSONFieldException("read-access-divesity>constraints>constraint>target>target-flavor") + arrayConstraintsFin.append(dictConstraint) + + res.update({"constraints":arrayConstraintsFin}) + return res + + @staticmethod + def readBearer(bearer : Dict) -> Dict: + res : Dict = {} + + res.update({"always-on":bearer.get("always-on")}) + res.update({"bearer-reference":bearer.get("bearer-reference")}) + res.update({"requested-type":bearer.get("requested-type",{}).get("requested-type")}) + res.update({"strict":bearer.get("requested-type",{}).get("strict")}) + + return res + + @staticmethod + def readIpConnection(ipConnection : Dict) -> Dict: #IPV4 E IPV6 TIENEN LOS MISMOS CAMPOS + res : Dict = {} + if ipConnection.get("ipv4") is not None: + res.update({"ipv4": ReadServiceFunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv4"))}) + elif ipConnection.get("ipv6") is not None: + res.update({"ipv6":ReadServiceFunctionDefinition.readIpv4_Ipv6(ipConnection.get("ipv6"))}) + # else: + # raise NoJSONFieldException("ip-connection>ipv4/6") + + bfd : Dict = ipConnection.get("oam",{}).get("bfd") + if bfd is not None: + oamFinal : Dict = {} + oamFinal.update({"enabled":bfd.get("enabled")}) + holdtime : Dict = bfd.get("holdtime") + holdtimeFinal : Dict = {} + if "fixed" in holdtime: + holdtimeFinal.update({"fixed":holdtime.get("fixed")}) + elif "profile" in holdtime: + holdtimeFinal.update({"profile":holdtime.get("profile")}) + # else: + # raise NoJSONFieldException("oam>bfd>holdtime") + + oamFinal.update({"holdtime":holdtimeFinal}) + res.update({"oam":oamFinal}) + + return res + + @staticmethod + def readIpv4_Ipv6(ipv : Dict) -> Dict : + res : Dict = {} + + res.update({"address-allocation-type":ipv.get("address-allocation-type")}) + + providerDHCP : Dict = ipv.get("provider-dhcp") #None si no existe ese campo, puede mejorarse eficiencia + if providerDHCP is not None: + providerDHCPFinal : Dict = {} + providerDHCPFinal.update({"provider-dhcp":providerDHCP.get("provider-address")}) + providerDHCPFinal.update({"prefix-length":providerDHCP.get("prefix-lenght")}) + addressAsign : Dict = providerDHCP.get("address-asign") + addressAsignFinal : Dict = {} + if "number" in addressAsign: + addressAsignFinal.update({"number":addressAsign.get("number")}) + elif "explicit" in addressAsign: + addressAsignFinal.update({"explicit":addressAsign.get("explicit")}) + # else: + # raise NoJSONFieldException("ipv4/6>address-asign") + + providerDHCPFinal.update({"address-asign":addressAsignFinal}) + res.update({"provider-dhcp":providerDHCPFinal}) + + + dhcpRelay : Dict = ipv.get("dhcp-relay") + if dhcpRelay is not None: + dhcpRelayFinal : Dict = {} + dhcpRelayFinal.update({"provider-address":dhcpRelay.get("provider-address")}) + dhcpRelayFinal.update({"prefix-lenght":dhcpRelay.get("prefix-lenght")}) + dhcpRelayFinal.update({"server-ip-address":dhcpRelay.get("customer-dhcp-servers",{}).get("server-ip-address")}) + res.update({"dhcp-relay":dhcpRelayFinal}) + + addresses : Dict = ipv.get("addresses") + if addresses is not None: + addressesFinal : Dict = {} + addressesFinal.update({"provider-address":addresses.get("provider-address")}) + addressesFinal.update({"customer-address":addresses.get("customer-address")}) + addressesFinal.update({"prefixe-length":addresses.get("prefixe-length")}) + res.update({"addresses":addressesFinal}) + + return res + + # @staticmethod + # def readSecurity(security : Dict) -> Dict: + # res : Dict = {} + + # res.update({"authentication" : security.get("authentication")}) + # encryption : Dict = security.get("encryption") + # encryptionFinal : Dict = {} + + + # return res + + #readService todavÃa me da error + @staticmethod + def readService(service : Dict) -> Dict : + res : Dict = {} + #try: + res.update({"svc-mtu":service.get("svc-mtu")}) + res.update({"svc-input-bandwidth":service.get("svc-input-bandwidth")}) + res.update({"svc-output-bandwidth":service.get("svc-output-bandwidth")}) + qos : Dict = service.get("qos") + + class_id = qos["qos-profile"]["classes"]["class"][0]["class-id"] + direction = qos["qos-profile"]["classes"]["class"][0]["direction"] + latency_boundary = qos["qos-profile"]["classes"]["class"][0]["latency"]["latency-boundary"] + guaranteed_bw_percent = qos["qos-profile"]["classes"]["class"][0]["bandwidth"]["guaranteed-bw-percent"] + + res.update({"class-id":class_id}) + res.update({"direction":direction}) + res.update({"latency-boundary":latency_boundary}) + res.update({"guaranteed-bw-percent":guaranteed_bw_percent}) + + #Clase que lee el campo custom varias lineas mas adelante + + def readCustom (custom : Dict) -> Dict: #custom es un Dict que es: {classes: [class-id: *, ..., latency: {flavor:{lowest:*///boundary:*}}],[...],[...]} + res : Dict = {} + + arrayClassesEntrada : List = custom.get("class") #un array con elementos indeterminados + arrayClassesFinal : List = [] + + for elementEntrada in arrayClassesEntrada: + elementFinal : Dict = {} + elementFinal.update({"class-id":elementEntrada.get("class-id")}) + elementFinal.update({"direction":elementEntrada.get("direction")}) + elementFinal.update({"rate-limit":elementEntrada.get("rate-limit")}) + #Campo latency---> + #latency: { + # flavor:{ + # lowest:* + # o quizás + # boundary:* + # } + # } + + latency : Dict = elementEntrada.get("latency") + jitter : Dict = elementEntrada.get("jitter") + if latency is not None: + if "use-lowest-latency" in latency: + elementFinal.update({"latency-flavor-lowest":latency.get("use-lowest-latency")}) + elif "latency-boundary" in latency: + elementFinal.update({"latency-flavor-boundary":latency.get("latency-boundary")}) + elif jitter is not None: + if "use-lowest-jitter" in jitter: + elementFinal.update({"jitter-flavor-lowest":jitter.get("use-lowest-jiter")}) + elif "latency-boundary" in jitter: + elementFinal.update({"jitter-flavor-boundary":jitter.get("latency-boundary")}) + else: + print("Debe haber un campo lowest o boundary dentro de jitter>flavor") + else: + print("FALTAN CAMPOS EN CUSTOM") + + bandwidthEntrada : Dict = elementEntrada.get("bandwidth") + bandwidthSalida : Dict = {} + bandwidthSalida.update({"guaranteed-bw-percent":bandwidthEntrada.get("guaranteed-bw-percent")}) + bandwidthSalida.update({"end-to-end":bandwidthEntrada.get("end-to-end")}) + elementFinal.update({"bandwidth":bandwidthSalida}) + + arrayClassesFinal.append(elementFinal) + + # res.update({"latency-flavor":arrayClassesFinal}) + # return res + # #END READCUSTOM + + # qosProfileEntrada : Dict = qos.get("qos-profile") + # qosProfileFin : Dict = {} + + # if "profile" in qosProfileEntrada: + # qosProfileFin.update({"standard-profile":qosProfileEntrada.get("standard")}) + # elif "classes" in qosProfileEntrada: + # qosProfileFin.update({"custom-classes":readCustom(qosProfileEntrada.get("classes"))}) + # else: + # print("falta campo en qos>qos-profile") + # #raise NoJSONFieldException("qos>qos-profile") + + # res.update({"qos-profile":qosProfileFin}) + # res.update({"carrierscarrier":qos.get("carrierscarrier")}) + # multicastEntrada : Dict = qos.get("multicast") + # multicastSalida : Dict = {} + # multicastSalida.update({"multicast-site-type":multicastEntrada.get("multicast-site-type")}) + # multicastAddressSalida : Dict = {} + # multicastAddressSalida.update({"ipv4":multicastEntrada.get("multicast-address-family", {}).get("ipv4")}) + # multicastAddressSalida.update({"ipv6":multicastEntrada.get("multicast-address-family", {}).get("ipv6")}) + # multicastSalida.update({"multicast-address-family":multicastAddressSalida}) + # multicastSalida.update({"protocol-type":multicastEntrada.get("protocol-type")}) + + # res.update({"multicast":multicastSalida}) + + # # except KeyError: + # # print("falta alguna clave en service") + # # except NoJSONFieldException as e: + # # print(e) + # # finally: + return res + + \ No newline at end of file diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/WriteNetworkFunctionDefinition.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/WriteNetworkFunctionDefinition.py new file mode 100644 index 0000000000000000000000000000000000000000..5c22de56dee9140467fb747c4d43db2a3ab25bcc --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/WriteNetworkFunctionDefinition.py @@ -0,0 +1,288 @@ +from typing import Dict, List +import json, os + +class WriteNetworkFunctionDefinition: + + input_file_path : str ="/home/ubuntu/Documents/EstructuraIntermedia.json" + output_file_path = "/home/ubuntu/Documents/L3VPN_ntw.json" + + jsonEstructuraIntermedia = open(input_file_path, "r") + jsonEstructuraIntermediaData : str = jsonEstructuraIntermedia.read() + jsonEstIntDict : Dict = json.loads(jsonEstructuraIntermediaData) + + @staticmethod + def numberVpnIDs() -> int : + vpnIDs : List = WriteNetworkFunctionDefinition.jsonEstIntDict["VPN_IDs"] + return len(vpnIDs) + + #Este metodo devuelve el numero de sites, y es necesario porque el numero de sites es el numero de nodes + @staticmethod + def numberSitesIDS() -> int : + sitesIDs : List = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sitesIDs"] + return len(sitesIDs) + + @staticmethod + def write() -> Dict: + + jsonNet : Dict = {} + jsonNet.update({"vpn-service":{}}) #no tenemos en cuenta que puede haber más de 1 VPN. Codigo valido para 1 VPN solo + vpn_id : str = WriteNetworkFunctionDefinition.jsonEstIntDict["VPN_IDs"][0].split("vpn")[1] #obtengo el numero de vpn_id + jsonNet["vpn-service"]["vpn-id"] = vpn_id #de momento solo 1 vpn + # jsonNet["vpn-service"]["customer-name"] = "_" + # jsonNet["vpn-service"]["parent-service-id"] = "_" + jsonNet["vpn-service"]["vpn-type"] = "L3VRF" + jsonNet["vpn-service"]["vpn-service-topology"] = "custom" #modificar todos estos campos + jsonNet["vpn-service"]["vpn-nodes"] = {} + + arrayVpnNode : List = [] + print(WriteNetworkFunctionDefinition.numberSitesIDS()) + for numeroDeNode in range(WriteNetworkFunctionDefinition.numberSitesIDS()): + + sites : List = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sitesIDs"] + + site: str = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sitesIDs"][numeroDeNode] + service_access: str = WriteNetworkFunctionDefinition.jsonEstIntDict["VPNs_Dict"]["vpn1"]["sites_Dict"][sites[numeroDeNode]]["site-network-accesses"]["site-network-accesses-IDs"][0] + + #con los siguientes ifs hago que las politicas cambien de lugar, uno si y uno no constantemente + if numeroDeNode % 2 is 0: + arrayVpnNode.append(WriteNetworkFunctionDefinition.writeNode(WriteNetworkFunctionDefinition.jsonEstIntDict,site,service_access,numeroDeNode, 0)) + if numeroDeNode % 2 is not 0: + arrayVpnNode.append(WriteNetworkFunctionDefinition.writeNode(WriteNetworkFunctionDefinition.jsonEstIntDict,site,service_access,numeroDeNode, 1)) + + jsonNet["vpn-service"]["vpn-nodes"]["vpn-node"] = arrayVpnNode + + #Codigo para crear el archivo y escribir el json en el + with open(WriteNetworkFunctionDefinition.output_file_path, 'w') as json_file: + json.dump(jsonNet, json_file, indent = 3) + + return jsonNet + + def writeNode(jsonEstDict : Dict, site: str, service_access : str, numeroDeNode : int, importExport : int) -> Dict: + res: Dict = {} + res.update({"ne-id": "pe" + str(numeroDeNode+1)}) + res.update({"vpn-node-id": "PE" + str(numeroDeNode+1)}) + res.update({"local-as":"65000"}) + res.update({"router-id":"5.5.5.5"}) + + #active-vpn-instance-profiles + activeVpnInstanceProfiles: Dict = {} + vpnInstanceProfilesArray: List = [] + vpnInstanceProfilesArrayObject: Dict = {} + + vpnInstanceProfilesArrayObject.update({"profile-id": "1"}) + routerID : Dict = {} + + #Pregutnar sobre si route distinguisher debe cambiar por cada node + directlyAsigned : Dict = {"rd": str(res.get("local-as")) + ":100"} + routerID.update({"directly-assigned":directlyAsigned}) + vpnInstanceProfilesArrayObject.update({"router-id":routerID}) + + addressFamily : List = [] + addressFamilyElement : Dict = {} + addressFamilyElement.update({"address-family":""}) + + vpnTargets: Dict = {} + vpnTarget: List = [] + vpnTargetObject: Dict = {} + vpnTargetObject.update({"id": str(numeroDeNode+1)}) + + routeTargets: List = [] + routeTargetsElement: Dict = {} + routeTargetsElement.update({"route-target": str(res.get("local-as"))+":222"}) #Any to any + routeTargets.append(routeTargetsElement) + + vpnTargetObject.update({"route-targets": routeTargets}) + vpnTargetObject.update({"route-target-type": "both"}) #Any to any + + vpnTarget.append(vpnTargetObject) + vpnTargets.update({"vpn-target": vpnTarget}) + + vpnPolicies: Dict = {} + #Codigo que en metodo write() hace que un nodo si y uno no vayan modificando el orden de politicas + if importExport is 0: + vpnPolicies.update({"import-policy":"politica 1"}) + vpnPolicies.update({"export-policy":"politica 2"}) + else: + vpnPolicies.update({"import-policy":"politica 2"}) + vpnPolicies.update({"export-policy":"politica 1"}) + vpnTargets.update({"vpn-policies":vpnPolicies}) + + addressFamilyElement.update({"vpn-targets":vpnTargets}) + addressFamily.append(addressFamilyElement) + + vpnInstanceProfilesArrayObject.update({"address-family": addressFamily}) + vpnInstanceProfilesArray.append(vpnInstanceProfilesArrayObject) + activeVpnInstanceProfiles.update({"vpn-instance-profile": vpnInstanceProfilesArray}) + res.update({"active-vpn-instance-profiles": activeVpnInstanceProfiles}) + + #vpn-network-accesses + vpnNetworkAccesses : Dict = {} + vpnNetworkAccessArray : List = [] + vpnNetworkAccessElement : Dict = {} + vpnNetworkAccessElement.update({"id":"1-1-1"}) + vpnNetworkAccessElement.update({"interface-id":"1-1-1"}) + vpnNetworkAccessElement.update({"vpn-network-access-type":"l3ipvlan"}) + vpnNetworkAccessElement.update({"vpn-instance-profiles":""}) + + status : Dict = { + "admin-status":{ + "status":"admin-up", + "last-change":"" + }, + "oper-status":{ + "status":"", + "last-change":"" + } + } + vpnNetworkAccessElement.update({"status":status}) + vpnNetworkAccessElement.update({"connection":WriteNetworkFunctionDefinition.writeConnection()}) + + vpn_id : str = jsonEstDict["VPN_IDs"][0]#valido cuando solo hay una VPN. + #de momento no IPv6, campo vacio, solo IPv4 + provider_address: str = jsonEstDict["VPNs_Dict"][vpn_id]["sites_Dict"][site]["site-network-accesses"]["site-network-accesses-Dict"][service_access]["ip-connection"]["ipv4"]["addresses"]["provider-address"] + prefixe_length : int = jsonEstDict["VPNs_Dict"][vpn_id]["sites_Dict"][site]["site-network-accesses"]["site-network-accesses-Dict"][service_access]["ip-connection"]["ipv4"]["addresses"]["prefixe-length"] + customer_address : str = jsonEstDict["VPNs_Dict"][vpn_id]["sites_Dict"][site]["site-network-accesses"]["site-network-accesses-Dict"][service_access]["ip-connection"]["ipv4"]["addresses"]["customer-address"] + + ipConnectionAndRoutingProtocols : Dict = { + "ip-connection":{ + "l3-termination-point":"1-1-1.222", + "ipv4":{ + "local-address":provider_address, + "prefixe-length":prefixe_length, + "address-allocation-type":"static-address", + "static-addresses":{ + "primary-addresses":"1", + "address":[ + { + "address-id":"1", + "customer-address":customer_address + } + ] + } + }, + "ipv6":{ + "local-address":"", + "prefixe-lenght":"", + "address-allocation-type":"static-address", + "static-addresses":{ + "primary-addresses":"1", + "address":[ + { + "address-id":"1", + "customer-address":"" + } + ] + } + } + }, + "routing-protocols":{ + "routing-protocol":[ + { + "id":"ver con oscar", + "type":"static-routing", + "routing-profiles":[ + { + "id":"", + "type":"" + + } + ], + "static":{ + "cascaded-lan-prefixes":{ + "ipv4-lan-prefixes":[ + { + "lan":"", + "next-hop":"", + "lan-tag":"", + "bfd-enable":"", + "metric":"", + "preference":"", + "status":{ + "admin-status":{ + "status":"", + "last-change":"" + }, + "oper-status":{ + "status":"", + "last-change":"" + } + }, + "tef-l3vpn-ntw:description":"" + } + ], + "ipv6-lan-prefixes":[ + { + "lan":"", + "next-hop":"", + "lan-tag":"", + "bfd-enable":"", + "metric":"", + "preference":"", + "status":{ + "admin-status":{ + "status":"", + "last-change":"" + }, + "oper-status":{ + "status":"", + "last-change":"" + } + }, + "tef-l3vpn-ntw:description":"" + } + ] + } + } + } + ] + } + } + + vpnNetworkAccessElement.update(ipConnectionAndRoutingProtocols) + + vpnNetworkAccessArray.append(vpnNetworkAccessElement) + vpnNetworkAccesses.update({"vpn-network-access":vpnNetworkAccessArray}) + res.update({"vpn-network-accesses":vpnNetworkAccesses}) + return res + + + #Falta "l2vpn-id":"", que es la otra choice entre l2vpn-id y l2-tunnel-service + def writeConnection() -> Dict: + res : Dict = { + "encapsulation":{ + "type":"untagged" + # , + # "dot1q":{ + # "tag-type":"cvlan", + # "cvlan-id":"222" + # } + # , + # "priotity-tagged":{ + # "tag-type":"" + # }, + # "quinq":{ + # "tag-type":"", + # "svlan-id":"", + # "cvlan-id":"" + # } + } + # , + # "l2-tunnel-service":{ + # "type":"", + # "pseudowire":{ + # "vcid":"", + # "far-end":"" + # }, + # "vpls":{ + # "vcid":"", + # "far-end":"" + # }, + # "vxlan":{ + # "vni-id":"", + # "peer-mode":"", + # "peer-ip-address":"" + # } + # } + } + + return res \ No newline at end of file diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/orignal_L3VPN_Services b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/orignal_L3VPN_Services new file mode 100644 index 0000000000000000000000000000000000000000..6bd57c8238c1af63ed3f504593f3c70cf8a68cc6 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/orignal_L3VPN_Services @@ -0,0 +1,80 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Dict, List +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType +from nbi.service.rest_server.nbi_plugins.tools.HttpStatusCodes import HTTP_CREATED, HTTP_SERVERERROR +from nbi.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH +from .Handlers import process_site, process_vpn_service +from .YangValidator import YangValidator + +LOGGER = logging.getLogger(__name__) + +class L3VPN_Services(Resource): + @HTTP_AUTH.login_required + def get(self): + return {} + + @HTTP_AUTH.login_required + def post(self): + if not request.is_json: raise UnsupportedMediaType('JSON payload is required') + request_data : Dict = request.json + LOGGER.debug('Request: {:s}'.format(str(request_data))) + + errors = list() + if 'ietf-l3vpn-svc:l3vpn-services' in request_data: + # processing multiple L3VPN service requests formatted as: + #{ + # "ietf-l3vpn-svc:l3vpn-services": { + # "l3vpn-svc": [ + # { + # "service-id": "vpn1", + # "vpn-services": { + # "vpn-service": [ + for l3vpn_svc in request_data['ietf-l3vpn-svc:l3vpn-services']['l3vpn-svc']: + l3vpn_svc.pop('service-id', None) + l3vpn_svc_request_data = {'ietf-l3vpn-svc:l3vpn-svc': l3vpn_svc} + errors.extend(self._process_l3vpn(l3vpn_svc_request_data)) + elif 'ietf-l3vpn-svc:l3vpn-svc' in request_data: + # processing single (standard) L3VPN service request formatted as: + #{ + # "ietf-l3vpn-svc:l3vpn-svc": { + # "vpn-services": { + # "vpn-service": [ + errors.extend(self._process_l3vpn(request_data)) + else: + errors.append('unexpected request: {:s}'.format(str(request_data))) + + response = jsonify(errors) + response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR + return response + + def _process_l3vpn(self, request_data : Dict) -> List[Dict]: + yang_validator = YangValidator('ietf-l3vpn-svc') + request_data = yang_validator.parse_to_dict(request_data) + yang_validator.destroy() + + errors = list() + + for vpn_service in request_data['l3vpn-svc']['vpn-services']['vpn-service']: + process_vpn_service(vpn_service, errors) + + for site in request_data['l3vpn-svc']['sites']['site']: + process_site(site, errors) + + return errors diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/run_tests_locally-nbi-ietf-l3vpn.sh b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/run_tests_locally-nbi-ietf-l3vpn.sh new file mode 100755 index 0000000000000000000000000000000000000000..0bd133e16caac438c8da1a5795fce89e99230c9a --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/run_tests_locally-nbi-ietf-l3vpn.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ + nbi/tests/test_ietf_l3vpn.py diff --git a/src/nbi/tests/test_ietf_l3vpn.py b/src/nbi/tests/test_ietf_l3vpn.py index 5c77744a6024803157f210212ee071eba3a3fbf1..95fdf095f9516be7c2fe924dbc35928e2f3cba07 100644 --- a/src/nbi/tests/test_ietf_l3vpn.py +++ b/src/nbi/tests/test_ietf_l3vpn.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging, pytest +import json, logging +import pytest from typing import Dict from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import ContextId diff --git a/src/service/service/service_handler_api/SettingsHandler.py b/src/service/service/service_handler_api/SettingsHandler.py index 293de54aa84be11f3c31bc1b47fce852df19a16a..2afa4cbbf4a8b2418aec16df99615e26a8ecbfba 100644 --- a/src/service/service/service_handler_api/SettingsHandler.py +++ b/src/service/service/service_handler_api/SettingsHandler.py @@ -47,6 +47,12 @@ class SettingsHandler: ACL_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/index[{:d}]/acl_ruleset[{:s}]' key_or_path = ACL_KEY_TEMPLATE.format(device_uuid, endpoint_name,endpoint_index, acl_ruleset_name) value = grpc_message_to_json(config_rule.acl) + elif kind == 'ip_link': + device_uuid = config_rule.ip_link.device1 + endpoint_uuid = config_rule.ip_link.interface + IP_LINK_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/ip_link' + key_or_path = IP_LINK_KEY_TEMPLATE.format(device_uuid, endpoint_uuid,) + value = config_rule.ip_link else: MSG = 'Unsupported Kind({:s}) in ConfigRule({:s})' LOGGER.warning(MSG.format(str(kind), grpc_message_to_json_string(config_rule))) @@ -100,6 +106,25 @@ class SettingsHandler: if not 'index[{:d}]'.format(acl_index) in res_key: continue acl_rules.append((res_key, res_value)) return acl_rules + + def get_endpoint_ip_link(self, device : Device, endpoint : EndPoint) -> List [Tuple]: + endpoint_name = endpoint.name + device_keys = device.device_id.device_uuid.uuid, device.name + endpoint_keys = endpoint.endpoint_id.endpoint_uuid.uuid, endpoint.name + ip_links = [] + for device_key in device_keys: + for endpoint_key in endpoint_keys: + endpoint_settings_uri = '/device[{:s}]/endpoint[{:s}]'.format(device_key, endpoint_key) + endpoint_settings = self.get(endpoint_settings_uri) + if endpoint_settings is None: continue + IP_LINK_KEY_TEMPLATE = '/device[{:s}]/endpoint[{:s}]/'.format(device_key, endpoint_name) + + results = dump_subtree(endpoint_settings) + for res_key, res_value in results: + if not res_key.startswith(IP_LINK_KEY_TEMPLATE): continue + if not "ip_link" in res_key: continue + ip_links.append((res_key, res_value)) + return None def set(self, key_or_path : Union[str, List[str]], value : Any) -> None: set_subnode_value(self.__resolver, self.__config, key_or_path, value) diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py index 52944a31c439472055d65e9e75249465dcbca7f7..84c561737d7582bdf6b7fe1a4b622de1b24a2f93 100644 --- a/src/webui/service/main/routes.py +++ b/src/webui/service/main/routes.py @@ -48,7 +48,7 @@ def process_descriptors(descriptors): descriptor_loader = DescriptorLoader(descriptors, num_workers=DESCRIPTOR_LOADER_NUM_WORKERS) results = descriptor_loader.process() for message,level in compose_notifications(results): - if level == 'error': LOGGER.warning('ERROR message={:s}'.format(str(message))) + if level == 'error': LOGGER.warning('ERROR message servicio ={:s}, {:s}'.format(str(message), str(descriptors))) flash(message, level) @main.route('/', methods=['GET', 'POST'])